`
啸笑天
  • 浏览: 3466258 次
  • 性别: Icon_minigender_1
  • 来自: China
社区版块
存档分类
最新评论

Method Swizzling

 
阅读更多

Thx: http://esoftmobile.com/2014/02/19/method-swizzling/

Method swizzling指的是改变一个已存在的选择器对应的实现的过程,它依赖于Objectvie-C中方法的调用能够在运行时进改变——通过改变类的调度表(dispatch table)中选择器到最终函数间的映射关系。

举个例子,假设我们想跟踪在一个iOS应用中每个视图控制器展现给用户的次数:

我们可以给每个视图控制器对应的viewWillAppear:实现方法中增加相应的跟踪代码,但是这样做会产生大量重复的代码。子类化可能是另一个选择,但要求你将UIViewController、 UITableViewController、 UINavigationController 以及所有其他视图控制器类都子类化,这也会导致代码重复。

幸好,还有另一个方法,在分类中进行method swizzling,下面来看怎么做:

#import <objc/runtime.h>

@implementation UIViewController (Tracking)

+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        Class class = [self class];

        // When swizzling a class method, use the following:
        // Class class = object_getClass((id)self);

        SEL originalSelector = @selector(viewWillAppear:);
        SEL swizzledSelector = @selector(xxx_viewWillAppear:);

        Method originalMethod = class_getInstanceMethod(class, originalSelector);
        Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);

        BOOL didAddMethod =
            class_addMethod(class,
                originalSelector,
                method_getImplementation(swizzledMethod),
                method_getTypeEncoding(swizzledMethod));

        if (didAddMethod) {
            class_replaceMethod(class,
                swizzledSelector,
                method_getImplementation(originalMethod),
                method_getTypeEncoding(originalMethod));
        } else {
            method_exchangeImplementations(originalMethod, swizzledMethod);
        }
    });
}

#pragma mark - Method Swizzling

- (void)xxx_viewWillAppear:(BOOL)animated {
    [self xxx_viewWillAppear:animated];
    NSLog(@"viewWillAppear: %@", self);
}

@end

在计算机学科中,指针变换(pointer swizzling)是指将基于名字或位置的引用转变为直接的指针引用。 然而在Objective-C中,这个词的起源并不完全知道,但关于这一借鉴其实也很好理解,method swizzling可以通过选择器来改变它引用的函数指针。

现在,当UIViewController或它子类的任何实例触发viewWillAppear:方法都会打印一条log日志。

向视图控制器的生命周期中注入操作、事件的响应、视图的绘制,或Foundation中的网络堆栈都是能够利用method swizzling产生明显效果的场景。还有一些其他的场景使用swizzling会是一个合适的选择,这随着Objective-C开发者经验不断丰富会变得越来越明显。

先不说为什么和在哪些地方使用swizzling,来看一下应该怎样实现:

+load vs. +initialize

Swizzling应该在+load方法中实现。

每个类的这两个方法会被Objective-C运行时系统自动调用,+load是在一个类最开始加载时调用,+initialize是在应用中第一次调用该类或它的实例的方式之前调用。这两个方法都是可选的,只有实现了才会被执行。

因为method swizzling会影响全局,所以减少冒险情况就很重要。+load能够保证在类初始化的时候就会被加载,这为改变系统行为提供了一些统一性。但+initialize并不能保证在什么时候被调用——事实上也有可能永远也不会被调用,例如应用程序从未直接的给该类发送消息。

dispatch_once

Swizzling应该在dispatch_once中实现。

还是因为swizzling会改变全局,我们需要在运行时采取所有可用的防范措施。保障原子性就是一个措施,它确保代码即使在多线程环境下也只会被执行一次。GCD中的diapatch_once就提供这些保障,它应该被当做swizzling的标准实践。

选择器、方法及实现

在Objective-C中,尽管这些词经常被放在一起来描述消息传递的过程,但选择器、方法及实现分别代表运行时的不同方面。

下面是苹果Objective-C Runtime Reference文档中对它们的描述:

  • 选择器(typedef struct objc_selector *SEL):选择器用于表示一个方法在运行时的名字,一个方法的选择器是一个注册到(或映射到)Objective-C运行时中的C字符串,它是由编译器生成并在类加载的时候被运行时系统自动映射。

  • 方法(typedef struct objc_method *Method):一个代表类定义中一个方法的不明类型。

  • 实现(typedef id (*IMP)(id, SEL, ...)):这种数据类型是实现某个方法的函数开始位置的指针,函数使用的是基于当前CPU架构的标准C调用规约。第一个参数是指向self的指针(也就是该类的某个实例的内存空间,或者对于类方法来说,是指向元类(metaclass)的指针)。第二个参数是方法的选择器,后面跟的都是参数。

理解这些概念之间关系最好的方式是:一个类(Class)维护一张调度表(dispatch table)用于解析运行时发送的消息;调度表中的每个实体(entry)都是一个方法(Method),其中key值是一个唯一的名字——选择器(SEL),它对应到一个实现(IMP)——实际上就是指向标准C函数的指针。

Method Swizzling就是改变类的调度表让消息解析时从一个选择器对应到另外一个的实现,同时将原始的方法实现混淆到一个新的选择器。

调用_cmd

下面这段代码看起来像是会导致一个死循环:

- (void)xxx_viewWillAppear:(BOOL)animated {
    [self xxx_viewWillAppear:animated];
    NSLog(@"viewWillAppear: %@", NSStringFromClass([self class]));
}

但其实并没有,在Swizzling的过程中,xxx_viewWillAppear:会被重新分配给UIViewController-viewWillAppear:的原始实现。一个优秀程序员应有的直觉会告诉你在一个方法的实现中通过self调用当前方法自身会产生错误,但是在当前这种情况下,如果我们记住到底是怎么回事更有意义。反而,如果我们在这个方法中调用viewWillAppear:才会真的导致死循环,因为这个方法的实现会在运行时被swizzle到viewWillAppear:的选择器。

记住给swizzled方法加上前缀,这和你需要给可能产生冲突的分类方法加前缀是一个道理。

注意事项

Swizzling被普遍认为是一种巫术,容易导致不可预料的行为和结果。尽管不是最安全的,但是如果你采取下面这些措施,method swizzling还是很安全的。

  • 始终调用方法的原始实现(除非你有足够的理由不这么做): API为输入和输出提供规约,但它里面具体的实现其实是个黑匣子,在Method Swizzling过程中不调用它原始的实现可能会破坏一些私有状态,甚至是程序的其他部分。

  • 避免冲突:给分类方法加前缀,一定要确保不要让你代码库中其他代码(或是依赖库)在做与你相同的事。

  • 理解:只是简单的复制粘贴swizzling代码而不去理解它是怎么运行的,这不仅非常危险,而且还浪费了学习Objective-C运行时的机会。阅读 Objective-C Runtime Reference 和 <objc/rumtime.h> 去理解代码是怎样和为什么这样执行的,努力的用你的理解来消灭你的疑惑。

  • 谨慎行事:不管你多么自信你能够swizzling Foundation、UIKit 或者其他内置框架,请记住所有这些都可能在下一个版本中就不好使。提前做好准备,防范于未然才不至于到时候焦头乱额。

 

不敢放心大胆的直接使用Objective-C运行时?Jonathan ‘Wolf’ Rentzsch提供了经过实战检验的、CocoaPads-ready的库JRSwizzle,它会为你考虑好了一些。

分享到:
评论
2 楼 啸笑天 2014-12-06  
http://blog.csdn.net/yiyaaixuexi/article/details/9374411
1 楼 啸笑天 2014-02-20  
http://blog.csdn.net/xiaoxiaotian2008/article/details/19521343
http://blog.csdn.net/xiaoxiaotian2008/article/details/19521361
http://blog.csdn.net/xiaoxiaotian2008/article/details/19521373

相关推荐

    iOS Method Swizzling

    **iOS Method Swizzling** 在iOS开发中,Method Swizzling是一种非常强大的技术,它允许开发者在运行时替换类的任何方法实现。这种方法交换是通过Objective-C的动态性来实现的,使得我们可以在不修改原有代码的情况...

    Method Swizzling示例

    **方法交换(Method Swizzling)详解** 在iOS和Mac OS X开发中,Objective-C作为主要的编程语言,提供了一种独特且强大的特性——方法交换(Method Swizzling)。它允许我们在运行时修改类的方法实现,这一特性在...

    Method Swizzling 和 AOP 实践1

    Method Swizzling 是Objective-C编程中的一种黑魔法技巧,它允许我们在运行时动态地改变方法的实现。这种方法在解决特定问题时非常有用,特别是在需要在不修改原有代码的情况下增强或扩展对象的行为时。在本篇文章中...

    swift-AopTestDemo:iOS埋点统计方案:1.MethodSwizzling2.AOP编程

    本示例项目"swift-AopTestDemo:iOS埋点统计方案"聚焦于两种常见的实现方法:Method Swizzling(方法替换)和Aspect-Oriented Programming(面向切面编程)。以下是对这两种技术的详细介绍。 **方法替换(Method ...

    MethodSwizzling:根据简书中的文章,写了一个Method Swizzling的小例子。希望各位能帮忙点个Star,谢谢!

    在简书的文章中详细的讲解了Method Swizzling,为了方便大家学习和理解,所以在Github上写了这个小Demo,可以帮助大家更好的理解Method Swizzling。 在Demo中简单实现了一个崩溃拦截的代码,等以后有时间的话,我...

    iOS 11 使用两种方法替换(Method Swizzling)去掉导航栏返回按钮的文字

    iOS 11 使用 Method Swizzling 去掉导航栏返回按钮的文字 在 iOS 11 中,去掉导航栏返回按钮的文字是一个常见的需求,特别是在设计上需要隐藏返回按钮的文字时。这种需求可以通过 Method Swizzling 来实现,Method ...

    详解iOS Method Swizzling使用陷阱

    在阅读团队一项目源码时,发现Method Swizzling的写法有些瑕疵。这篇文章主要就介绍iOS Method Swizzling的正确写法应该是什么样的。 下面是iOS Method Swizzling的一种实现: + (void)load { Class class = [self...

    Mehod Swizzling实现页面统计功能

    **Method Swizzling 实现页面统计功能** 在iOS开发中,我们常常需要对应用程序中的页面浏览情况进行统计,以了解用户的行为模式、优化用户体验或进行数据分析。Method Swizzling 是 Objective-C 运行时(Runtime)...

    UIViewController+Swizzling 实现页面统计

    为了实现这一目标,一种常见的技术手段是利用Objective-C的Method Swizzling(方法替换)。本篇文章将深入探讨如何通过`UIViewController+Swizzling`来实现在不修改原有代码的情况下,对页面视图控制器的生命周期...

    Method_Swizzling:方法交叉和AOP编程的小例子,仅供参考

    在iOS和Mac开发中,Objective-C是一门广泛使用的编程语言,它提供了许多强大的特性,其中之一就是Method Swizzling。Method Swizzling是一种运行时技术,允许开发者修改类的方法实现,即在程序运行时动态替换一个...

    swift-利用AOP的思想通过方法交换(MethodSwizzle黑魔法修改ios系统类库方法)的做法

    本文将深入探讨如何利用AOP的思想,通过方法交换(Method Swizzling)这一“黑魔法”来修改iOS系统类库的方法,实现登录权限的统一管理。 方法交换是Objective-C中的一个特性,但在Swift中也可以通过桥接头文件来...

    ios UITableView实现无数据加载占位图片

    在这个场景中,我们通过方法交换(Method Swizzling)将系统的`reloadData`方法替换为我们自定义的`kk_reloadData`方法。 方法交换主要通过以下步骤实现: 1. 获取原始方法(`reloadData`)和要交换的方法(`kk_...

    解决iOS中常见的几种Crash1

    本文主要探讨了在iOS中常见的几种Crash类型及其无侵入式的解决方案,特别是利用Method Swizzling来预防这些问题。 首先,数组越界是iOS开发中一个常见的崩溃原因。当尝试访问数组中不存在的索引时,程序会崩溃。为...

    Runtime应用实例

    例如,我们可能想要在每个网络请求的方法调用前后添加日志记录,就可以通过Method Swizzling实现。使用`method_exchangeImplementations()`函数,我们可以将原有的方法实现与新的实现进行交换。这种方法需要注意的是...

    看懂App加载Class的顺序和Method的顺序.zip

    例如,文件名为UIViewController-Swizzled可能表明项目中对UIViewController进行了方法交换(Method Swizzling),这是一种运行时修改类方法的技术,可以用来替换原有方法的行为,实现AOP(面向切面编程)。...

    ios-SafeKit:防止crash.zip

    原理是method swizzling替换了系统方法,处理参数边界,现多个项目使用,一切正常 原名DurexKit,名字太无节操,改为SafeKit SafeKit中针对每个可能crash的方法处理,目前处理的范围有限,如有兴趣,欢迎贡献代码...

    ios-runtime理解和实践.zip

    9. **性能优化**:Runtime可以用于性能优化,例如通过Method Swizzling替换性能较低的原生方法,或者利用 Associated Objects避免使用KVC带来的性能损失。 在"RunTime实践"中,你可能会学习如何使用这些技术来解决...

    非越狱手机hook技术

    它主要依赖于动态库注入和Method Swizzling等方法,使得开发者能够在运行时修改应用的行为,通常用于测试、调试或者增强应用的功能。以下是关于非越狱手机Hook技术的详细解释: 1. **动态库注入**:在iOS系统中,...

    Runtime面试题.pdf

    在运行时,可以利用动态特性,通过selector找到对应的IMP,并通过method swizzling技术替换掉原来的IMP,达到挂钩的目的。交换方法的实现可以通过method_exchangeImplementations,class_replaceMethod或method_set...

Global site tag (gtag.js) - Google Analytics