<簡書 — 劉小壯> https://www.jianshu.com/p/f313e8e32946html
當一個對象的方法被調用時,首先在對象所屬的類中查找方法列表,若是當前類中沒有則向父類查找,一直找到根類NSObject
。若是始終沒有找到方法實現,則進入消息轉發步驟中。git
當一個方法沒有實現時,也就是在cache lsit
和其繼承關係的method list
中,沒有找到對應的方法。這時會進入消息轉發階段,可是在進入消息轉發階段前,Runtime
會給一次機會動態添加方法實現。github
能夠經過重寫resolveInstanceMethod:
和resolveClassMethod:
方法,動態添加未實現的方法。其中第一個是添加實例方法,第二個是添加類方法。這兩個方法都有一個BOOL
返回值,返回NO
則進入消息轉發機制。app
void dynamicMethodIMP(id self, SEL _cmd) {
// implementation ....
}
+ (BOOL)resolveInstanceMethod:(SEL)sel {
if (sel == @selector(resolveThisMethodDynamically)) {
class_addMethod([self class], sel, (IMP) dynamicMethodIMP, "v@:");
return YES;
}
return [super resolveInstanceMethod:sel];
}
複製代碼
在經過class_addMethod
函數動態添加實現時,後面有一個"v@:"
來描述SEL
對應的函數實現,具體的描述能夠參考官方文檔。ide
在進行消息轉發以前,還能夠在forwardingTargetForSelector:
方法中將未實現的消息,轉發給其餘對象。能夠在下面方法中,返回響應未實現方法的其餘對象。函數
- (id)forwardingTargetForSelector:(SEL)aSelector {
NSString *selectorName = NSStringFromSelector(aSelector);
if ([selectorName isEqualToString:@"selector"]) {
return object;
}
return [super forwardingTargetForSelector:aSelector];
}
複製代碼
當forwardingTargetForSelector:
方法未作出任何響應的話,會來到消息轉發流程。消息轉發時會首先調用methodSignatureForSelector:
方法,在方法內部生成NSMethodSignature
類型的方法簽名對象。在生成簽名對象時,能夠指定target
和SEL
,能夠將這兩個參數換成其餘參數,將消息轉發給其餘對象。佈局
[otherObject methodSignatureForSelector:otherSelector];
複製代碼
生成NSMethodSignature
簽名對象後,就會調用forwardInvocation:
方法,這是消息轉發中最後一步了,若是在這步尚未對消息進行處理,則會致使崩潰。學習
這個方法中會傳入一個NSInvocation
對象,這個對象就是經過剛纔生成的簽名對象建立的,能夠經過invocation
調用其餘對象的方法,調用其invokeWithTarget:
便可。ui
- (void)forwardInvocation:(NSInvocation *)anInvocation {
if ([object respondsToSelector:[anInvocation selector]]) {
[anInvocation invokeWithTarget:object];
} else {
[super forwardInvocation:anInvocation];
}
}
複製代碼
將一條消息發送給一個不能處理的對象會引發崩潰,可是在崩潰以前,系統給響應對象一次處理異常的機會。編碼
當發送一條對象不能處理的消息,產生Crash
以前,系統會調用響應者的forwardInvocation:
方法,並傳入一個NSInvocation
對象,NSInvocation
對象中包含原始消息及參數。這個方法只有方法未實現的時候纔會調用。
你能夠實現forwardInvocation:
方法,將消息轉發給另外一個對象。forwardInvocation:
方法是一個動態方法,在響應者沒法響應方法時,會調用forwardInvocation:
方法,能夠重寫這個方法實現消息轉發。
消息轉發中forwardInvocation:
須要作的是,確認消息將發送到哪裏,以及用原始參數發送消息。能夠經過invokeWithTarget:
方法,發送被轉發的消息。調用invoke
方法後,原方法的返回值將被返回給調用方。
- (void)forwardInvocation:(NSInvocation *)anInvocation {
if ([someOtherObject respondsToSelector:[anInvocation selector]]) {
[anInvocation invokeWithTarget:someOtherObject];
} else {
[super forwardInvocation:anInvocation];
}
}
複製代碼
forwardInvocation:
方法不僅能夠處理一個方法,能夠經過Selector
進行判斷,來處理多個須要轉發的方法。
在OC中有時候能夠動態的提供方法實現,例如屬性能夠經過@dynamic propertyName;
的形式,表示將在運行過程當中動態的提供屬性方法。
若是想實現動態方法解析,須要實現resolveInstanceMethod:
或resolveClassMethod:
方法,在這兩個方法中動態的添加方法實現。經過class_addMethod
方法能夠動態添加方法,添加方法時須要關聯對應的函數指針,函數指針須要聲明兩個隱藏參數self
和_cmd
。
void dynamicMethodIMP(id self, SEL _cmd) {
// implementation ....
}
+ (BOOL) resolveInstanceMethod:(SEL)aSEL {
if (aSEL == @selector(resolveThisMethodDynamically)) {
class_addMethod([self class], aSEL, (IMP) dynamicMethodIMP, "v@:");
return YES;
}
return [super resolveInstanceMethod:aSel];
}
複製代碼
消息轉發和動態方法解析大部分是相同的,在消息轉發以前一個類有機會動態解析這個方法。若是已經調用respondsToSelector:
或instancesRespondToSelector:
方法,動態方法解析有機會優先爲Selector
添加IMP
。若是你實現了resolveInstanceMethod:
方法,但想要特定的Selector
走消息轉發流程,則將此方法返回NO
便可。
能夠經過消息轉發機制來模擬多繼承,例以下面這張圖中,兩個類中雖然不存在繼承關係,可是卻由另外一個類處理了Warrior
的事件。
由上面的例子能夠看出,分屬兩個繼承分支的類,經過消息轉發機制實現了繼承的關係。Warrior
的negotiate
消息由其「父類」Diplomat
來實現。
經過消息轉發實現的多重繼承相對於普通繼承來講更有優點,消息轉發能夠將消息轉發給多個對象,這樣就能夠將代碼按不一樣職責封裝爲不一樣對象,並經過消息轉發給不一樣對象處理。
須要注意的是,Warrior
雖然經過消息轉發機制能夠響應negotiate
消息,但若是經過respondsToSelector:
和isKindOfClass:
方法進行判斷的話,依然是返回NO
的。若是想讓這兩個方法能夠在判斷negotiate
方法時返回YES
,須要重寫這兩個方法並在其中加入判斷邏輯。
- (BOOL)respondsToSelector:(SEL)aSelector {
if ([super respondsToSelector:aSelector]) {
return YES;
} else {
//
}
return NO;
}
複製代碼
在執行forwardInvocation:
以前,須要經過methodSignatureForSelector:
方法返回方法簽名,若是不實現則用默認的方法簽名。
在方法簽名的過程當中,能夠將未實現的方法轉發給其代理。
- (NSMethodSignature*)methodSignatureForSelector:(SEL)selector {
NSMethodSignature* signature = [super methodSignatureForSelector:selector];
if (!signature) {
signature = [surrogate methodSignatureForSelector:selector];
}
return signature;
}
複製代碼
在項目中常常會出現由於調用未實現的方法,致使程序崩潰的狀況。在學習消息轉發後,就能夠經過消息轉發來解決這個問題。
全部的類的基類都是NSObject
類(NSProxy
除外),能夠將NSObject
類的消息轉發流程攔截,而後作一些統一的處理,這樣就能夠解決方法未實現致使的崩潰。根據Category
能夠將原類方法「覆蓋」的特色,能夠在Category
中實現相應的攔截方法。
// 自定義類
#import <Foundation/Foundation.h>
@interface TestObject : NSObject
- (void)testMethod;
@end
// 接收消息的IMP
void dynamicResolveMethod(id self, SEL _cmd) {
NSLog(@"method forward");
}
// 對NSObject建立的Category
@implementation NSObject (ExceptionForward)
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-protocol-method-implementation"
+ (BOOL)resolveInstanceMethod:(SEL)sel {
const char *types = sel_getName(sel);
class_addMethod([self class], sel, (IMP)dynamicResolveMethod, types);
return YES;
}
#pragma clang diagnostic pop
@end
複製代碼
咱們的攔截方案是在resolveInstanceMethod
方法中,動態建立未實現的方法,並將IMP
統一設置爲dynamicResolveMethod
函數進行處理。這樣全部未實現的方法都會執行dynamicResolveMethod
函數,而不崩潰,在dynamicResolveMethod
函數中能夠作崩潰統計等操做。
在OC中是不支持多繼承的,可是能夠經過消息轉發模擬多繼承。在子類中實例化多個父類,當消息發送過來的時候,在消息轉發的方法中,將調用重定向到父類的實例對象中,以實現多繼承的效果。
下面是多繼承的例子,建立兩個父類Cat
和Dog
,並將須要子類繼承的方法都定義到Protocol
中,在Cat
和Dog
中實現Protocol
中的方法。
@protocol CatProtocol <NSObject>
- (void)eatFish;
@end
@interface Cat : NSObject <CatProtocol>
@end
@implementation Cat
- (void)eatFish {
NSLog(@"Cat Eat Fish");
}
@end
@protocol DogProtocol <NSObject>
- (void)eatBone;
@end
@interface Dog : NSObject
@end
@implementation Dog
- (void)eatBone {
NSLog(@"Dog Eat Bone");
}
@end
複製代碼
子類直接經過遵照父類的協議,來表示本身「繼承」哪些類,並在內部實例化對應的父類對象。在外界調用協議方法時,子類實際上是沒有實現這些父類的方法的,因此經過轉發方法將消息轉發給響應的父類。
@interface TestObject : NSObject <CatProtocol, DogProtocol>
@end
@interface TestObject()
@property (nonatomic, strong) Cat *cat;
@property (nonatomic, strong) Dog *dog;
@end
@implementation TestObject
- (id)forwardingTargetForSelector:(SEL)aSelector {
if ([self.cat respondsToSelector:aSelector]) {
return self.cat;
} else if ([self.dog respondsToSelector:aSelector]) {
return self.dog;
} else {
return self;
}
}
// 忽略Cat和Dog的初始化過程
@end
複製代碼
簡書因爲排版的問題,閱讀體驗並很差,佈局、圖片顯示、代碼等不少問題。因此建議到我Github
上,下載Runtime PDF
合集。把全部Runtime
文章總計九篇,都寫在這個PDF
中,並且左側有目錄,方便閱讀。
下載地址:Runtime PDF 麻煩各位大佬點個贊,謝謝!😁