消息转发三部曲及应用
调用一个没有实现的方法,程序会crash,报错找不到方法:
unrecognized selector sent to instance 0x7fcf6c70c780
但其实,在程序crash前,我们还是可以救一救的,这就涉及到消息转发过程:
objc在向一个对象发送消息时,runtime库会根据对象的isa指针找到该对象所属的类,然后在该类的方法列表中寻找对应方法,如果找不到,去父类方法列表找,如果去到最顶层的父类中都找不到,在程序崩溃前,运行时runtime会试图拯救崩溃。
1. Method resolution
(理解:询问具体的方法接收类: 如果该方法没有实现的话, 你能不能现在动态添加一个实现出来?)
根据调用的方法是对象方法还是类方法重写下面的方法动态添加一个方法实现:
对象方法会调用+resolveInstanceMethod:
类方法 +resolveClassMethod:
如果有重写方法并添加实现,就走正常的消息发送流程,如果没有或方法return NO,试图消息转发给别的对象(跳到第2步)
其中IMP可以用以下写法,给当前类动态添加当前消息的实现:
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
if (sel == @selector(sendMessage:)) {
class_addMethod([self class], sel, imp_implementationWithBlock(^(id self, NSString *parm) {
NSLog(@"parm = %@", parm);
}), "v@*");
}
return YES;
}
注:"v@*"写法参考Type Encodings
也可以给别的类动态添加方法,再调用
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
class_addMethod([Person class], @selector(logSomething:), (IMP) logSomething, "v@:@");
Person *person = [Person new];
if ([person respondsToSelector:@selector(logSomething:)]) {
[person performSelector:@selector(logSomething:) withObject:@"something"];
}else{
NSLog(@"不动态添加实现");
return NO;
}
return YES;
}
/**
* 动态添加的方法实现
*/
void logSomething(id self, SEL _cmd, NSString *str){
NSLog(@"%@",str);
}
2. Fast forwarding
(二次询问: 没有动态添加实现没关系, 可以告诉我谁有这个消息的对应实现? 我可以把消息转发给他)
如果目标对象实现了-forwardingTargetForSelector:
runtime 这时就会调用这个方法,如果返回一个能响应该消息的对象, 那么消息会转发到返回对象那里, 如果返回nil或者返回对象不能相应此消息, 进行第3步。
3. Normal forwarding
( 不会再有询问过程, 而是直接将消息携带的一切信息包裹在NSInvocation中交给对象自己处理)
首先它会发送-methodSignatureForSelector:消息获得函数的方法签名(参数和返回值类型)。如果-methodSignatureForSelector:返回nil,Runtime则会发出-doesNotRecognizeSelector:消息,程序会crash。
如果返回了一个函数签名,Runtime就会创建一个NSInvocation对象并发送-forwardInvocation:消息给目标对象。
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
NSMethodSignature *sig = [super methodSignatureForSelector:aSelector];
if (!sig) {
//NSPointerArray *delegates;//不用NSArray,避免循环引用。(NSArray内对象元素会被强引)
for (id delegate in self.delegates) {
if(!delegate)continue;
sig = [delegate methodSignatureForSelector:aSelector];
if (sig) {
break;
}
}
}
return sig;
}
- (void)forwardInvocation:(NSInvocation *)anInvocation {
for (id delegate in self.delegates) {
if ([delegate respondsToSelector:anInvocation.selector]) {
[anInvocation invokeWithTarget:delegate];
}
}
}
应用:
1、多代理,如第3步,用一个NSPointerArray的属性存储代理对象,提供对属性的增删查改方法,可根据index操作。利用消息转发来实现多个代理响应。
2、动态添加实现等方法可以拦截crash,可按需降低crash可能性。