内容简介:Method Swizzle的本质是在运行时交换方法实现(IMP),一般是在原有的方法中,插入自己的业务需求。Objective-C的消息机制:在 Objective-C 中调用一个方法, 实际上是在底层通过 objc_msgSend()发送一个消息。 而查找消息的唯一依据是selector的方法名。每一个OC实例对象都保存有isa指针和实例变量,其中isa指针所属类,类维护一个运行时可接收的方法列表(MethodLists); 方法列表(MethodLists)中保存selector & IMP的映射关系
Method Swizzle的本质是在运行时交换方法实现(IMP),一般是在原有的方法中,插入自己的业务需求。
原理
Objective-C的消息机制:在 Objective-C 中调用一个方法, 实际上是在底层通过 objc_msgSend()发送一个消息。 而查找消息的唯一依据是selector的方法名。
[obj doSomething]; /// => objc_msgSend(obj,@selector(doSomething)) 复制代码
每一个OC实例对象都保存有isa指针和实例变量,其中isa指针所属类,类维护一个运行时可接收的方法列表(MethodLists); 方法列表(MethodLists)中保存selector & IMP的映射关系。在运行时,通过selecter找到匹配的IMP,从而找到的具体的实现函数。
开发中可以利用Objective-C的动态特性,在运行时替换selector对应的方法实现(IMP),达到给hook的目的。下图是利用 Method Swizzle 来替换selector对应IMP后的方法列表示意图。
例子
在description() 之前打印“description 被 Swizzle 了”这样的日志。
@implementation NSObject (Swizzle) + (void)load{ //调换IMP Method originalMethod = class_getInstanceMethod([NSObject class], @selector(description)); Method newMethod = class_getInstanceMethod([NSObject class], @selector(replace_description)); method_exchangeImplementations(originalMethod, newMethod); } - (void)replace_description{ NSLog(@"description 被 Swizzle 了"); [self replace_description]; } @end 复制代码
使用swizzle时,我们应该注意哪些问题呢?
问题一:继承问题
如果 originalMethod 是其父类实现的,那么直接 method_exchangeImplementations 是把父类中的 originalMethod 给替换了,导致该父类以及其他子类调用的 originalMethod 也会被替换
解决: 通过 class_addMethod 判断 method 是不是属于本类自己实现的?
- class_addMethod 返回 YES -> addMethod 成功,class中不存在 method,也就是存在父类中。addMethod之后,当前class也就存在method 了(覆盖了父类的方法)
- class_addMethod 返回 NO -> addMethod 失败,class中存在 method,说明当前方法属于当前class
- 判断之后,再执行 exchange
代码:
@implementation Model (Swizzle) + (void)load { Class class = [self class]; SEL originalSelector = @selector(hhh); SEL swizzledSelector = @selector(new_hhh); Method originalMethod = class_getInstanceMethod(class, originalSelector); Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector); // 添加 originalSelector->swizzle method 到 class BOOL success = class_addMethod(class, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod)); if (success) { // 说明originalSelector在父类中 class_replaceMethod(class, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod)); } else { // 说明originalSelector在当前类中 method_exchangeImplementations(originalMethod, swizzledMethod); } } @end 复制代码
问题二:方法的参数会被改变
如果 originalMethod 中使用了 _cmd参数,可能造成bug
@interface IncorrectSwizzleClass : NSObject - (void) swizzleExample; - (void) originalMethod; @end @implementation IncorrectSwizzleClass - (void)swizzleExample { Method m1 = class_getInstanceMethod([self class], @selector(originalMethod)); Method m2 = class_getInstanceMethod([self class], @selector(replaceImp)); method_exchangeImplementations(m1, m2); } - (void)originalMethod { NSLog(@"方法名为 originalMethod,其 _cmd 的值为:%@",[NSString stringWithFormat:@"*** -%@", NSStringFromSelector(_cmd)]); } - (void)replaceImp { /* * 添加自己的逻辑:比如添加log */ [self replaceImp]; } @end - (void)incorrect { NSLog(@"#################### incorrect #######################"); IncorrectSwizzleClass* example2 = [[IncorrectSwizzleClass alloc] init]; NSLog(@"## swizzle 之前,调用 originalMethod 的打印信息:"); [example2 originalMethod]; [example2 swizzleExample]; NSLog(@"## swizzle 之后,调用 originalMethod 的打印信息:"); [example2 originalMethod]; } 复制代码
打印结果:
分析: 执行 OC方法时,默认会传递两个参数(self & _cmd) [self replaceImp]; /// 会被编译器变成 objc_msgSend(self, @selector(replaceImp)),方法的第二个参数是 @“replaceImp”,故 originalMethod 中打印的是 replaceImp。
解决:C方法+ method_setImplementation 的方式
@interface CorrectSwizzleClass : NSObject - (void) swizzleExample; - (void) originalMethod; @end static IMP __original_Method_Imp; void replaceImp(id self, SEL _cmd) { /* * 添加自己的逻辑:比如添加 log */ ((int(*)(id,SEL))__original_Method_Imp)(self, _cmd); } @implementation CorrectSwizzleClass - (void)swizzleExample { Method m = class_getInstanceMethod([self class],@selector(originalMethod)); /// method_setImplementation:return The previous implementation of the method __original_Method_Imp = method_setImplementation(m,(IMP)replaceImp); } - (void)originalMethod { NSLog(@"方法名为 originalMethod,其 _cmd 的值为:%@",[NSString stringWithFormat:@"*** -%@", NSStringFromSelector(_cmd)]); } @end - (void)correct { NSLog(@"#################### correct #######################"); CorrectSwizzleClass* example = [[CorrectSwizzleClass alloc] init]; NSLog(@"## swizzle 之前,调用 originalMethod 的打印信息:"); [example originalMethod]; [example swizzleExample]; NSLog(@"## swizzle 之后,调用 originalMethod 的打印信息:"); [example originalMethod]; } 复制代码
打印结果:
问题三:如何做到对象级别的 swizzle?
只对某个对象进行 swizzle,不影响其他对象
方案:
- 类本身支持。可以标记一下,在执行方法时,判断是否存在标记来判断是否执行swizzle 之后的方法。可以参考:第三方库 DZNEmptyDataSet(统一空白页)
- 动态生成一个当前对象所属类的子类,并将当前对象与子类关联。这样的话,swizzle的都是其子类的方法,不会影响父类。可以参考:第三方库 Aspects
聊一下Aspects
Aspects属于AOP编程的库,源码总数不超过1000行,对外就暴露了两个方法。 使用方式:可以hook 类方法、对象实例方法,还有三种执行位置:before、insert、after
@interface NSObject (Aspects) + (id<AspectToken>)aspect_hookSelector:(SEL)selector withOptions:(AspectOptions)options usingBlock:(id)block error:(NSError **)error; - (id<AspectToken>)aspect_hookSelector:(SEL)selector withOptions:(AspectOptions)options usingBlock:(id)block error:(NSError **)error; @end 复制代码
例子:
/** * 事件拦截 * 拦截UIViewController的viewDidLoad方法 */ [UIViewController aspect_hookSelector:@selector(viewDidLoad) withOptions:AspectPositionAfter usingBlock:^(id<AspectInfo> aspectInfo) { /** * 添加我们要执行的代码,由于withOptions 是 AspectPositionAfter。 * 所以每个控制器的 viewDidLoad 触发都会执行下面的方法 */ [self doSomethings]; } error:NULL]; - (void)doSomethings { //TODO: 比如日志输出、统计代码 NSLog(@"------"); } 复制代码
简单原理:
- 把待 hook 的 originalSelector 生成 aliasSelector
- 把待 hook 的 originalSelector 添加前缀aspects_ -> aliasSelector -> 用 block & aliasSelector生成 aspectContainer
- 通过 associated 把 aspectContainer 绑定到 self( 对象或class),key为 aliasSelector
- 把 originalSelector 的 IMP 设置为 _objc_msgForward(会触发消息转发,不会查询方法列表了)
- swizzle forwardInvocation
- 在自定义的 forwardInvocation 中通过 associated & selector -> aliasSelector 获取 aspectContainer
- 根据 before/insert/after 的规则执行 originalSelector & block
_objc_msgForward
_objc_msgForward
是一个函数指针(和 IMP 的类型一样),是用于消息转发的:当向一个对象发送一条消息,但它并没有实现的时候, _objc_msgForward
会直接走消息转发。看一个不存在方法的例子:
:heart:hook对象
动态生成一个当前对象的子类,并将当前对象与子类关联,然后替换子类的 forwardInvocation 方法(具体参考源码)。那么就可以将当前对象变成一个子类的实例,同时对于外部使用者而言,仍可以把它继续当成原对象使用,而且所有的 swizzle 操作都发生在子类,这样做的好处是你不需要去更改对象本身的类
以上所述就是小编给大家介绍的《浅谈 iOS swizzle》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。