本文是按照博客点击打开链接学习runtime,整理备份,方便温故知新。
iOS的动态性主要体现在runtime中,即在程序运行阶段可以调用任意方法,移花接木等黑科技,各种调用。
关于消息机制,先补充几点:
1.调用方法的本质是调用objc_msgSend函数发送消息。在发送消息途中可以任意转发,截留消息,实现上述的移花接木之类的黑科技。
2.在正常情况下,如果调用的函数都是实现好的,则不用考虑消息转发,只需要按照调用类->父类->父类的顺序挨着找就行了。如果一直都没有找到,则需要考虑消息转发。如果不做转发处理,则会直接cash;
3.消息转发主要是三级处理,每一级我们都可以单独处理,处理好了就可以在没找到函数的情况下处理事件,防止cash(找不到指的是在object的methodList中找不到)。三级分别是,动态方法解析,备用接收者,完整转发,如下图:
少说废话,直接上三个消息转发的demo
1.动态方法解析
废话:在收到异常消息的时候,动态的添加c方法,并实现异常消息处理,如下
+ (BOOL)resolveInstanceMethod:(SEL)sel{
//从系统里匹配SEL,如果没有就注册SEL
SEL systemSel = sel_registerName(sel_getName(sel));
//把所有未知的SEL指向dynamicMethodIMP的实现,让其帮忙打印错误信息
class_addMethod(self, systemSel, (IMP)dynamicMethodIMP, "v@:");
BOOL boolValue = [super resolveClassMethod:systemSel];
return boolValue;
}
void dynamicMethodIMP(id self, SEL _cmd){
NSString * className = NSStringFromClass([self class]);
NSString * selName = NSStringFromSelector(_cmd);
NSLog(@"%@:不是%@的方法",className,selName);
}
2.备用接收者
废话:当前target是找不到方法了,可以推给备胎,让备胎去处理。这样处理的话方法调用者并不知道是其他对象处理的,很隐蔽,利于封装。而且容易实现,代码量少。
- (id)forwardingTargetForSelector:(SEL)aSelector{
return [[Girl alloc] init];
}
3.完整消息转发
废话:其实这也是找其他对象接受消息,相比2更任意一些。而且还可以集中转移到某个类,可以让该类实现类似于多继承的效果,excited!
//在这里产生方法签名,以确保NSInvocation能被转发的girl类执行,不然的话会出现错误
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
NSMethodSignature * signature = [super methodSignatureForSelector:aSelector];
if (!signature) {
if ([Girl instancesRespondToSelector:aSelector]) {
signature = [Girl instanceMethodSignatureForSelector:aSelector];
}
}
return signature;
}
- (void)forwardInvocation:(NSInvocation *)anInvocation{
if ([Girl instancesRespondToSelector:anInvocation.selector]) {
[anInvocation invokeWithTarget:[[Girl alloc]init]];
}
}
总结:
在一个函数找不到时,OC提供了三种方式去补救:
1、调用resolveInstanceMethod给个机会让类添加这个实现这个函数
2、调用forwardingTargetForSelector让别的对象去执行这个函数
3、调用forwardInvocation(函数执行器)灵活的将目标函数以其他形式执行。
如果都不中,调用doesNotRecognizeSelector抛出异常。
疑问
Q1:那我们只用最后一个接盘侠方法多好啊,为什么还需要前2个呢?
其实还与这3个方法的用途不同有关:
运行期添加方法,用1;
转发给另1个对象、改变方法时,用2;
需要转发给多个对象时,用3;
而且,步骤越往后,处理消息的代价越大,到最后一个阶段时,都创建了 NSInvocation 对象了。
Q2:消息转发有哪些应用场景呢?
可以在运行期再加入某方法,例如 Teacher 类里有teach方法,DrugDealer 类里有letsCook方法,通过一号接盘侠方法,我们可以在运行期把 saleDrug 偷摸加到 teacher 的方法列表中,让 teacher 具备贩毒的功能,[teacher guessWhatHeDo],实际调用的是[teacher letsCook],唉呀妈呀,绝命毒师啊。
把方法转给其他对象处理,再举个例子,还是 Teacher 类(博主跟老师有仇吗...),[teacher letsCook],可以把对象在运行期换为drugDealer。再来一个 Cook 类,也有 letsCook 方法,但这次这方法不是 cook 毒品,而是 cook 菜。因此既可以通过[teacher letsCook] 实现[drugDealer letsCook],也可以实现[cook letsCook]。相当于 OC 实现了多重继承,虽然有点不太恰当...