在對象上調用方法,是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;
}