内容简介: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》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Data Mining
Jiawei Han、Micheline Kamber、Jian Pei / Morgan Kaufmann / 2011-7-6 / USD 74.95
The increasing volume of data in modern business and science calls for more complex and sophisticated tools. Although advances in data mining technology have made extensive data collection much easier......一起来看看 《Data Mining》 这本书的介绍吧!