Object-c 运行时允许你修改selector(method name)到implementation(the method code itself)的映射,在不修改 iOS 系统类库或第三方类库的源码基础上,修改原有调用逻辑。
也就是说替换两个方法的实现,相当于Hook。
下面先看一个例子:
比如我们想替换NSObject中的lastObject方法,先定义替换方法:
1 2 3 4 5 | - (id)xxx_lastObject{ id ret = [self xxx_lastObject]; NSLog(@"************* xxx_lastObject *******"); return ret; } |
然后进行如下替换:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | void class_swizzleMethod(Class c, SEL originalSelector, SEL swizzledSelector) { Method originalMethod = class_getInstanceMethod(c, originalSelector); Method swizzledMethod = class_getInstanceMethod(c, swizzledSelector); BOOL didAddMethod = class_addMethod(c, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod)); if(didAddMethod){ class_replaceMethod(c, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod)); }else{ method_exchangeImplementations(originalMethod, swizzledMethod); } } |
再尝试调用lastObject方法:
1 2 3 4 | NSArray *array = @[@"0",@"1",@"2",@"3",@"4",@"5"]; NSString *lastObject = [array lastObject]; NSLog(@"lastObject reslut:%@",lastObject); |
输出结果:
原理:
OC的方法是一种叫Method的结构体,这种objc_method类型的结构体定义为:
1 2 3 4 5 | struct objc_method SEL method_name Method method_types IMP method_imp } |
1.选择器(typedef struct objc_selector *SEL):选择器用于表示一个方法在运行时的名字,一个方法的选择器是一个注册到(或映射到)Objective-C运行时中的C字符串,它是由编译器生成并在类加载的时候被运行时系统自动映射。
2.方法(typedef struct objc_method *Method):一个代表类定义中一个方法的不明类型。
3.实现(typedef id (*IMP)(id, SEL, …)):这种数据类型是实现某个方法的函数开始位置的指针,函数使用的是基于当前CPU架构的标准C调用规约。第一个参数是指向self的指针(也就是该类的某个实例的内存空间,或者对于类方法来说,是指向元类(metaclass)的指针)。第二个参数是方法的选择器,后面跟的都是参数。
理解这些概念之间关系最好的方式是:一个类(Class)维护一张调度表(dispatch table)用于解析运行时发送的消息;调度表中的每个实体(entry)都是一个方法(Method),其中key值是一个唯一的名字——选择器(SEL),它对应到一个实现(IMP)——实际上就是指向标准C函数的指针。
而我们上面的操作其实就是交换了两个函数的method_imp,也就是说调用lastObject的时候其实会去调用xxx_lastObject的实现函数。
应该注意的地方:
1.+load vs. +initialize
Swizzling应该在+load方法中实现。
每个类的这两个方法会被Objective-C运行时系统自动调用,+load是在一个类最开始加载时调用,+initialize是在应用中第一次调用该类或它的实例的方式之前调用。这两个方法都是可选的,只有实现了才会被执行。
因为method swizzling会影响全局,所以减少冒险情况就很重要。+load能够保证在类初始化的时候就会被加载,这为改变系统行为提供了一些统一性。但+initialize并不能保证在什么时候被调用——事实上也有可能永远也不会被调用,例如应用程序从未直接的给该类发送消息。
2.dispatch_once
Swizzling应该在dispatch_once中实现。
还是因为swizzling会改变全局,我们需要在运行时采取所有可用的防范措施。保障原子性就是一个措施,它确保代码即使在多线程环境下也只会被执行一次。GCD中的diapatch_once就提供这些保障,它应该被当做swizzling的标准实践。
3.避免冲突
给分类方法加前缀,一定要确保不要让你代码库中其他代码(或是依赖库)在做与你相同的事。
其它的大家可以参考:http://stackoverflow.com/questions/5339276/what-are-the-dangers-of-method-swizzling-in-objective-c
Permalink
全是代码啊
Permalink
也有说明,主要看代码实现把。、
Permalink
看了博主的文章,真的很有收获啊
Permalink
欢迎一起交流~