iOS runtime学习之消息转发机制

在对象上调用方法,是OC中经常使用的功能。用OC的术语来说,这叫做“消息传递”。
在很多语言中,比如C,调用函数就是跳转到内存中某一点开始执行代码,没有任何动态性可言,因为在编译期就决定了。而OC不同,是在运行时发送消息的。这个消息,也许会由对象自己处理,也可能被转发给另一个对象,或者不予理睬。下面就说一下消息传递是如何工作的:
(1)检查接受对象是否为nil,如果是,调用nil处理程序
(2)检查类缓存中是不是已经有了这个方法,有了,就直接调用。
(3)比较请求的选择子和类中定义的选择子,找到了,调用其实现
(4)比较请求的选择子和父类中的选择子,再到父类的父类,以此类推,找到了,调用其实现
(5)以上四步还找不到,就要用到消息转发机制了
(6)调用resolveInstanceMethod:(或resolveClassMethod:)方法。如果返回YES,那么重新开始,这个时候对象会响应这个选择子,一般因为它已经调用了class_addMethod方法。
(7)(如果6的返回为NO)调用快速转发方法给备援接受者,forwardingTargetForSelector:,如果返回非nil,就把消息发送到返回的对象上。这里不要返回self,否则会形成死循环。
(8)调用methodSignatureForSelector:,如果返回非nil,创建一个NSInvocation对象,把与尚未处理的那条消息有关的全部细节封装于其中。此对象包括选择子、目标(target)及参数。将此对象传给forwardInvocation:方法。
(9)调用doesNotRecognizeSelector:,默认的实现是抛出异常。
消息转发流程:
这里写图片描述

例子一:(7)验证

@implementation Test1

- (id)forwardingTargetForSelector:(SEL)aSelector {
    if ([NSStringFromSelector(aSelector) isEqualToString:@"eat"]) {
        return [[Test2 alloc] init];
    }
    return nil;
}
@end

@implementation Test2

- (void)eat {
    NSLog(@"test2 : eat");
}
@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Test1 *test1 = [[Test1 alloc] init];
        objc_msgSend(test1, @selector(eat), @"DiSanXian");
    }
    return 0;
}

例子二:(8)验证
(8)方法可以简单的实现,只需要改变调用目标,将消息在调用目标上实现即可。然而这样做和(7)就“等效”了,消息传递到这其实开销还是挺大的,越往下开销越大,所以很少有人这么实现。那么问题就来了,该怎么实现呢?比较有用的实现方式是:在触发消息之前,先以某种方式改变消息内容,比如,追加另外一个参数,或者改变选择子,等等。

@implementation Test1

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    if ([NSStringFromSelector(aSelector) isEqualToString:@"eat"]) {
        return [NSMethodSignature signatureWithObjCTypes:"v@:@"];
    }
    return [super methodSignatureForSelector:aSelector];
}

- (void)forwardInvocation:(NSInvocation *)anInvocation {
    anInvocation.selector = @selector(eatWithFood:);
    [anInvocation invokeWithTarget:[[Test2 alloc] init]];
}
@end

@implementation Test2

- (void)eatWithFood:(NSString *)name {
    NSLog(@"foodName: %@", name);
}

@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Test1 *test1 = [[Test1 alloc] init];
        objc_msgSend(test1, @selector(eat), @"DiSanXian");

    }
    return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章