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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章