iOS消息轉發(fā)之 - "臣妾做不到"
一池颈、崩潰問題產生的過程:
Objective-C的方法調用實際是一種消息傳遞,當向objective-c對象發(fā)送一個消息時陈莽,Runtime如果在當前類及父類中找不到此selector對應的方法互亮,在執(zhí)行一個消息轉發(fā)的流程后愁铺,最終產生一個崩潰,出現(xiàn)Unrecognized selector sent to instance xxx問題蒙兰,實在是找不到可以接收消息的對象時磷瘤,才會拋出一個崩潰錯誤(讓我處理這消息,真心做不到啊)搜变。
如下圖:
消息轉發(fā)過程的關鍵方法:
動態(tài)方法解析
向當前類發(fā)送resolveInstanceMethod:消息采缚,檢查是否動態(tài)向類添加了方 法,如果返回YES挠他,則系統(tǒng)認為方法已經被添加扳抽,則會重新發(fā)送消息。快速消息轉發(fā)
檢查當前類是否實現(xiàn)forwardingTargetForSelector:方法殖侵,若實現(xiàn)則調 用贸呢,如果方法返回值為非nil或非self的對象,則向返回的對象重新發(fā)送消息拢军。標準消息轉發(fā)
Runtime發(fā)送methodSignatureForSelector:消息獲取selector對應方法的簽名楞陷,如果有方法簽名返回,則根據方法簽名創(chuàng)建描述消息的NSInvocation茉唉,向當前對象發(fā)送forwardInvocation:消息固蛾,如果沒有方法簽名返回,即返回值為nil赌渣,則向當前對象發(fā)送doesNotRecognizeSelector:消息魏铅,應用崩潰退出
二、崩潰問題規(guī)避方法
當向某個對象發(fā)送消息坚芜,Runtime在當前類和父類中都找不到對應方法實現(xiàn)時览芳,應用并不會立即崩潰退出,而是先執(zhí)行一個完整的消息轉發(fā)流程才會結束鸿竖。這也就給了我們去修正問題的機會沧竟。
1). 有準備才能抓住機會 —— 實現(xiàn)動態(tài)加載方法
如果你有意識到此類崩潰問題,并期望可以在運行時有機會添加缺失的方法缚忧,那么你就可以通過實現(xiàn)NSObject的resolveInstanceMethod:
方法悟泵,并利用class_addMethod
方法動態(tài)添加函數(shù)。如下:
+ (BOOL)resolveInstanceMethod:(SEL)sel {
NSLog(@"%s >>>> %@", __func__, NSStringFromSelector(sel));
BOOL resolved = [super resolveInstanceMethod:sel];
if (!resolved) {
// 動態(tài)添加一個方法_dynamic_method_imp_處理消息
class_addMethod([self class], sel, (IMP)_dynamic_method_imp_, "v@:");
return YES; // 返回YES闪水,表示消息轉發(fā)成功糕非,不會發(fā)生崩潰
}
return resolved;
}
2). 再次改過自新的機會 —— 快速消息轉發(fā)
如果你沒有采用動態(tài)加載方法處理此類問題,即不實現(xiàn)NSObject的resolveInstanceMethod:
方法,你也可以實現(xiàn)NSObject的forwardingTargetForSelector:
方法朽肥,以聲明一個新的類對象來處理這個消息禁筏。如下:
- (id)forwardingTargetForSelector:(SEL)aSelector {
NSLog(@"%s >>> %@",__func__, NSStringFromSelector(aSelector));
id cls = [super forwardingTargetForSelector:aSelector];
if (cls == nil) {
// 使用代理類處理消息(自定義的一個類)
ForwardProxy *p = [[ForwardProxy alloc] init];
if ([p respondsToSelector:aSelector]) {
return p; // 返回非nil,非self的對象衡招,表示消息轉發(fā)成功篱昔,不會發(fā)生崩潰
}
}
return cls;
}
3). 機不可失,失不再來 —— 標準消息轉發(fā)
如果你只想規(guī)避此類問題始腾,那你可以通過實現(xiàn)NSObject的methodSignatureForSelector:和forwardInvocation:方法來進行消息的轉發(fā)處理州刽,以規(guī)避此類問題。方法methodSignatureForSelector:返回一個任意一個非nil的NSMethodSignature對象浪箭,就可以進入到forwardInvocation:方法穗椅,在這個方法里可以轉發(fā)消息,也可以什么都不做山林。
如下:
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
NSLog(@"%s >>> %@", __func__, NSStringFromSelector(aSelector));
NSMethodSignature *ms = [super methodSignatureForSelector:aSelector];
if (ms == nil) {
// 創(chuàng)建一個非nil的方法簽名房待,否則鹃栽,不會進入forwardInvocation:方法進行消息轉發(fā)
ms = [ForwardProxy instanceMethodSignatureForSelector:@selector(missMethod)];
}
return ms;
}
- (void)forwardInvocation:(NSInvocation *)anInvocation {
NSLog(@"forward invocation: %@", anInvocation);
if (anInvocation) {
// 處理轉發(fā)的消息塑陵,進入此方法篓足,就不會產生崩潰
[self missTarget:[anInvocation target] withSelector:[anInvocation selector]];
}
}
注意:實現(xiàn)forwardInvocation:方法時阎毅,不用調用super forwardInvocation:方法蕴茴,否則顶霞,應用仍然會崩潰垦巴。
本文的部份內容摘自:騰訊Bugly特邀文章