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>