iOS開發-消息轉發
消息轉發是OC運行時比較重要的特性,Objective-C運行時的主要的任務是負責消息分發,我們在開發中 " unrecognized selector sent to instance xx",實例對象沒有實現對應的消息,通常我們只需要實現未實現的方法即可。一般情況我們處理一個方法,運行時尋找匹配的selector然后執行,但是有時候只想在運行時才創建某個方法,消息確沒有具體的實現,這個時候就會出出現運行時錯誤,按照消息轉發的順序我們有三種解決辦法。
動態方法處理
首先我們來看一個簡單的例子,定義一個Message類,定義一個responseMethod方法,不實現方法,直接調用:
Message *msg=[[Message alloc]init]; [msg responseMethod];
錯誤信息如下:
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[Message responseMethod]: unrecognized selector sent to instance 0x10011ad20'
動態方法的重寫有兩個可以調用方法:
+ (BOOL)resolveClassMethod:(SEL)sel OSX_AVAILABLE_STARTING(MAC_10_5, __IPHONE_2_0);
- (BOOL)resolveInstanceMethod:(SEL)sel OSX_AVAILABLE_STARTING(MAC_10_5, __IPHONE_2_0);</pre>
此時我們只需要需要重寫 +resolveInstanceMethod: 返回YES就可以解決錯誤信息,注意我們需要通過class_method方法添加新的函數取代原有的sel:
+(BOOL)resolveInstanceMethod:(SEL)sel{ NSLog(@"FlyElephant-
} return [super resolveClassMethod:sel];class_addMethod([self class],sel, (IMP) dynamicMethodIMP, "v@:"); return YES;
} +(BOOL)resolveClassMethod:(SEL)sel{ return [super resolveClassMethod:sel]; }</pre>
動態執行的函數:
void dynamicMethodIMP(id self, SEL _cmd) { NSLog(@"Developer--dynamicMethodIMP--%@",NSStringFromSelector(_cmd)); }
消息轉發
如果上面的方法沒有重寫或者說是返回NO,那么我們接下來的按照順序還有兩種選擇,兩種選擇的原理都是一樣,消息有對應的target,我們需要更換對應的target即可:
- (id)forwardingTargetForSelector:(SEL)aSelector __OSX_AVAILABLE_STARTING(__MAC_10_5, __IPHONE_2_0);
forwardingTargetForSelector返回參數是一個對象,如果對象非nil、非self,系統會將運行的消息轉發給這個對象執行,否則會執行第三種解決方案。
-(id)forwardingTargetForSelector:(SEL)aSelector{ NSLog(@"FlyElephant-http://www.cnblogs.com/xiaofeixiang/"); NSLog(@"forwardingTargetForSelector"); if (aSelector==@selector(responseMethod)) { return developer; } return self; }
第二種消息是將消息發送到另外一個對象,如果想要修改消息,那么就要使用 -forwardInvocation: ,運行時將消息打包成NSInvocation,然后返回給你處理。處 理完之后,調用 i nvokeWithTarget,但是如果是是調用-forwardInvocation是無法執行成功,在執行之前我們進行方法簽名。
-(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{ NSLog(@"methodSignatureForSelector"); if ([super respondsToSelector:aSelector]) { return [super methodSignatureForSelector:aSelector]; }else{ return [developer methodSignatureForSelector:aSelector]; } }-(void)forwardInvocation:(NSInvocation *)anInvocation{ NSLog(@"forwardInvocation"); SEL sel=[anInvocation selector]; if ([developer respondsToSelector:sel]) { [anInvocation invokeWithTarget:developer]; }else{ [super forwardInvocation:anInvocation]; } }</pre>
關于Developer類中的方法:
@implementation Developer-(void)responseMethod{ NSLog(@"博客園:FlyElephant-
@end</pre>
Cocoa中代理(Proxies)和響應鏈(Responder Chain)用到了消息轉發。NSProxy是一個輕量級的class,它的作用就是轉發消息到另一個object。如果想要惰性加載object的某個屬性會很有用。NSUndoManager也有用到,不過是截取消息,之后再執行,而不是轉發到其他的地方。
響應鏈是關于Cocoa如何處理與發送事件與行為到對應的對象,通常我們處理的鍵盤文本框事件 First Responder,如果沒有處理該消息,則轉發到下一個 -nextResponder 。這么一直下去直到找到能夠處理該消息的object,或者沒有找到,報錯。
</div> </span>