Objective-C中的Method Swizzling

来源:互联网 发布:kontakt mac 音源入库 编辑:程序博客网 时间:2024/06/05 03:26

Objective-C中的Method Swizzling

了解Method Swizzling前应该先来了解一下Runtime与方法相关的一些内容。

1.基础数据类型

SEL

SEL,选择器,是表示一个方法的selector的指针,定义如下:

typedef struct objc_selector *SEL;

方法的selector用于表示运行时方法的名字。Objective-C在编译时,会根据每一个方法的名字,生成一个唯一的整型标识(Int类型的地址),这个标识就是SEL。

SEL reloadSel = @selector(reloadData);

SEL的本质是一个指向方法的指针(是根据方法名hash化了的KEY值,能唯一代表一个方法)。两个方法只要方法名相同,那么方法的SEL就是一样的。每一个方法都对应着一个SEL。同一个类(及类的继承体系)中,不能存在2个同名的方法,即使参数类型不同也不行。不同的类可以拥有相同的selector,不同类的实例对象执行相同的selector时,会在各自的方法列表中去根据selector去寻找自己对应的IMP。

IMP

IMP是一个函数指针,指向方法实现的首地址。定义如下:

id (*IMP)(id, SEL, ...)

第一个参数是指向self的指针(如果是实例方法,则是类实例的内存地址;如果是类方法,则是指向元类的指针),第二个参数是方法选择器(selector),后面是方法的实际参数列表。

SEL就是为了查找方法的最终实现IMP的。由于每个方法对应唯一的SEL,因此我们可以通过SEL方便快速准确地获得它所对应的 IMP。取得IMP后,就获得了执行这个方法代码的入口点,然后就可以像调用普通的C语言函数一样来使用这个函数指针了。

通过取得IMP,我们可以跳过Runtime的消息传递机制,直接执行IMP指向的函数实现,这样省去了Runtime消息传递过程中所做的一系列查找操作,会比直接向对象发送消息高效一些。

Method

Method用于表示类定义中的方法,定义如下:

typedef struct objc_method *Method;struct objc_method {    SEL method_name             OBJC2_UNAVAILABLE; // 方法名    char *method_types          OBJC2_UNAVAILABLE;    IMP method_imp              OBJC2_UNAVAILABLE; // 方法实现}

这个结构体中包含一个SEL和IMP,相当于在SEL和IMP之间作了一个映射。有了SEL,就可以找到对应的IMP,从而调用方法的实现代码。

2.方法相关操作函数

Runtime提供了一系列的方法来处理与方法相关的操作。包括方法本身及SEL。

// 调用指定方法的实现id method_invoke ( id receiver, Method m, ... );// 调用返回一个数据结构的方法的实现void method_invoke_stret ( id receiver, Method m, ... );// 获取方法名SEL method_getName ( Method m );// 返回方法的实现IMP method_getImplementation ( Method m );// 获取描述方法参数和返回值类型的字符串const char * method_getTypeEncoding ( Method m );// 获取方法的返回值类型的字符串char * method_copyReturnType ( Method m );// 获取方法的指定位置参数的类型字符串char * method_copyArgumentType ( Method m, unsigned int index );// 通过引用返回方法的返回值类型字符串void method_getReturnType ( Method m, char *dst, size_t dst_len );// 返回方法的参数的个数unsigned int method_getNumberOfArguments ( Method m );// 通过引用返回方法指定位置参数的类型字符串void method_getArgumentType ( Method m, unsigned int index, char *dst, size_t dst_len );// 返回指定方法的方法描述结构体struct objc_method_description * method_getDescription ( Method m );// 设置方法的实现IMP method_setImplementation ( Method m, IMP imp );// 交换两个方法的实现void method_exchangeImplementations ( Method m1, Method m2 );

接下来真正来看Method Swizzling的内容。

3.Method Swizzling

Method Swizzling是改变一个selector的实际实现的技术。通过它,我们可以在运行时通过修改类的分发表中selector对应的函数,来修改方法的实现。

当我们需要在整个工程中修改系统某个类某个方法的实现时,一种方法是创建一个子类重写这个方法,然后其他类都继承这个类,不过这种方法在工程开始之初还可以,但是如果是在一个已有工程的基础上新加需求,如果修改全部的类都继承这个类,改动量就比较大了。这时候就可以用Method Swizzling来实现。

举个例子:

@implementation UIViewController (LoadView)+ (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(viewDidLoad:);        SEL swizzledSelector = @selector(s_viewDidLoad:);        Method original_ViewDidLoad = class_getInstanceMethod(class, originalSelector);        Method swizzled_ViewDidLoad = class_getInstanceMethod(class, swizzledSelector);        method_exchangeImplementations(original_ViewDidLoad, swizzled_ViewDidLoad);    });}- (void)s_viewDidLoad {    [self initLoadView];    [self s_viewDidLoad];}- (void)initLoadView {    // add load view}@end

在上面的例子中,我们通过method swizzling修改了UIViewController的@selector(viewDidLoad:)对应的函数指针,让它的实现指向了我们自定义的s_viewDidLoad的实现。这样,当UIViewController及其子类的对象调用viewDidLoad时,都会执行initLoadView来添加load view。

通过上面的例子,可以总结出几个method swizzling的用法和注意事项
1. method swizzling要在+load中执行
由于method swizzling会影响到类的全局状态,而+load能保证在类的初始化过程中被加载,且能够保证这种改变应用级别的行为的一致性,所以要在+load中执行。
2. method swizzling要在dispatch_once中执行
同样由于method swizzling会影响到类的全局状态,所以需要在运行时采取一些预防措施。原子性就是这样一种措施,可以确保代码只被执行一次,不管有多少个线程。GCD的dispatch_once完全可以确保这种行为。
3. 为了避免冲突,要给自定义的方法加前缀,从而可以避免和所依赖的代码库出现命名冲突。
4. 无限循环,上面的例子中,在s_viewDidLoad中调用了[self s_viewDidLoad:animated],咋看上去会导致死循环。但是,在swizzling的过程中,s_viewDidLoad已经被重定向UIViewController类的viewDidLoad:中,不会产生无限循环。但是如果我们调用的是[self viewDidLoad:animated],则会产生无限循环,因为这个方法的实现在运行时已经被重新指定为s_viewDidLoad了。

0 0
原创粉丝点击