1、方法method和selector(選擇子)有什么關系
在 Objective-C 中,selector,Method 和 implementation(IMP) 都是 Runtime 的組成部分。在實際開發(fā)中它們常常是可以相互轉換來處理消息的發(fā)送的。選擇子代表方法在 Runtime 期間的標識符。為 SEL 類型,雖然 SEL 是 objc_selector 結構體指針,但實際上它只是一個 C 字符串。在類加載的時候,編譯器會生成與方法相對應的選擇子,并注冊到 Objective-C 的 Runtime 運行系統(tǒng)。
得出結論:選擇子其實是方法的名稱,不同類中方法名相同參數不同的倆個方法,他們的選擇子是相同的。
Method的結構體
/// Method
struct objc_method {
SEL method_name;
char *method_types;
IMP method_imp;
};
- 方法名 method_name 類型為 SEL,前面提到過相同名字的方法即使在不同類中定義,它們的方法選擇器也相同。
- 方法類型 method_types 是個 char 指針,其實存儲著方法的參數類型和返回值類型,即是 Type Encoding 編碼。(即類型編碼)
- method_imp 指向方法的實現(xiàn),本質上是一個函數的指針,就是前面講到的 Implementation。
消息傳遞
在OC中,給對象發(fā)送消息的語法是:
id returnValue = [someObject messageName:parameter];
這里,someObject叫做
接收者(receiver)
,messageName:叫做選擇子(selector)
,選擇子和參數合起來稱為“消息”。編譯器看到此消息后,將其轉換為一條標準的C語言函數調用,所調用的函數乃是消息傳遞機制中的核心函數叫做objc_msgSend,它的原型如下:
void objc_msgSend(id self, SEL cmd, ...)
第一個參數代表接收者,第二個參數代表選擇子,后續(xù)參數就是消息中的那些參數,數量是可變的·,所以這個函數就是參數個數可變的函數。
因此,上述以OC形式展現(xiàn)出來的函數就會轉化成如下函數:
id returnValue = objc_msgSend(someObject,@selector(messageName:),parameter);
可以看出,在調用方法時,編譯器將它轉成了objc_msgSend
消息發(fā)送了,在Runtime的執(zhí)行過程如下
- 1、Runtime先通過對象
someobject
找到isa
指針,判斷isa指針是否為nil
,為nil
直接return。 - 2、若不為空則通過
isa
指針找到當前實例的類對象,在類對象下查找緩存是否有messageName
方法。 - 3、若在類對象緩存中找到
messageName
方法,則直接調用IMP
方法(本質上是函數的指針)。 - 4、若在類對象緩存中沒找到
messageName
方法,則查找當前類對象的方法列表methodlist
,若找到方法則將其添加到類對象的緩存中。 - 5、若在類對象方法列表中沒找到
messageName
方法,則繼續(xù)到當前類的父類中以相同的方式查找(即類的緩存->類的方法列表)。 - 6、若在父類中找到
messageName
方法,則將IMP
添加到類對象緩存中。 - 7、若在父類中沒找到
messageName
方法,則繼續(xù)查詢父類的父類,直到追溯到最上層NSObject
。 - 8、若還是沒有找到,則啟用動態(tài)方法解析、備用接收者、消息轉發(fā)三部曲,給程序最后一個機會
- 9、若還是沒找到,則Runtime會拋出異常
doesNotRecognizeSelector
。
綜上,方法的查詢流程基本就是查詢類對象中的緩存和方法列表->父類中的緩存和方法列表->父類的父類中的緩存和方法列表->...->NSObject中的緩存和方法列表->動態(tài)方法解析->備用接收者->消息轉發(fā)。
消息動態(tài)解析
Objective-C 運行時會調用 +resolveInstanceMethod:
或者 +resolveClassMethod:
,讓你有機會提供一個函數實現(xiàn)。前者在 對象方法未找到時 調用,后者在 類方法未找到時 調用。我們可以通過重寫這兩個方法,添加其他函數實現(xiàn),并返回 YES
, 那運行時系統(tǒng)就會重新啟動一次消息發(fā)送的過程。
主要用的的方法如下:
// 類方法未找到時調起,可以在此添加方法實現(xiàn)
+ (BOOL)resolveClassMethod:(SEL)sel;
// 對象方法未找到時調起,可以在此添加方法實現(xiàn)
+ (BOOL)resolveInstanceMethod:(SEL)sel;
/**
* class_addMethod 向具有給定名稱和實現(xiàn)的類中添加新方法
* @param cls 被添加方法的類
* @param name selector 方法名
* @param imp 實現(xiàn)方法的函數指針
* @param types imp 指向函數的返回值與參數類型
* @return 如果添加方法成功返回 YES,否則返回 NO
*/
BOOL class_addMethod(Class cls, SEL name, IMP imp,
const char * _Nullable types);
測試代碼
main.m 文件中
int main(int argc, const char * argv[]) {
@autoreleasepool {
}
Person *xiaoming = [[Person alloc]init];
[xiaoming performSelector:@selector(way)];
return 0;
}
person.m 文件中
// 重寫 resolveInstanceMethod: 添加對象方法實現(xiàn)
+ (BOOL)resolveInstanceMethod:(SEL)sel{
if (sel == @selector(way)) {
class_addMethod([self class], sel,class_getMethodImplementation([self class], @selector(method)), "123");//使用class_addMethod動態(tài)添加方法method
}
return YES;
}
- (void)method{
NSLog(@"進入了消息動態(tài)解析");
}
運行了上述的測試代碼后,我們會發(fā)現(xiàn)即便我們并沒有實現(xiàn)way方法,而且使用了performSelector去強行調用way方法,但是我們的程序并沒有崩潰,是因為在查找了類方法和所有的父類后還是沒有找到way方法,程序進入了消息動態(tài)解析,然后我們使用了class_addMethod去動態(tài)添加方法method,最后程序從調用
performSelector和直接調用方法的區(qū)別
performSelector: withObject:
是在iOS中的一種方法調用方式。他可以向一個對象傳遞任何消息,而不需要在編譯的時候聲明這些方法。所以這也是runtime
的一種應用方式。
所以performSelector
和直接調用方法的區(qū)別就在與runtime
。直接調用編譯是會自動校驗。如果方法不存在,那么直接調用 在編譯時候就能夠發(fā)現(xiàn),編譯器會直接報錯。
但是使用performSelector
的話一定是在運行時候才能發(fā)現(xiàn),如果此方法不存在就會崩潰。所以一般使用performSelector
的時候,一般都會使用- (BOOL)respondsToSelector:(SEL)aSelector;
來在運行時判斷對象是否響應此方法。
消息接受者重定向(備用接受者)
如果上一步中 +resolveInstanceMethod:
或者 +resolveClassMethod:
沒有添加其他函數實現(xiàn),運行時就會進行下一步:消息接受者重定向。
如果當前對象實現(xiàn)了 -forwardingTargetForSelector:
或者 +forwardingTargetForSelector:
方法,Runtime 就會調用這個方法,允許我們將消息的接受者轉發(fā)給其他對象。
其中用到的方法。
// 重定向類方法的消息接收者,返回一個類或實例對象
+ (id)forwardingTargetForSelector:(SEL)aSelector;
// 重定向方法的消息接收者,返回一個類或實例對象
- (id)forwardingTargetForSelector:(SEL)aSelector;
注意:
- 類方法和對象方法消息轉發(fā)第二步調用的方法不一樣,前者是
+forwardingTargetForSelector:
方法,后者是-forwardingTargetForSelector:
方法。- 這里
+resolveInstanceMethod:
或者+resolveClassMethod:
無論是返回YES
,還是返回NO
,只要其中沒有添加其他函數實現(xiàn),運行時都會進行下一步。
測試代碼
- (id)forwardingTargetForSelector:(SEL)aSelector{
if (aSelector == @selector(way)) {
Friends *friends = [[Friends alloc]init];
return friends;//返回friends對象,讓friends對象接受這個消息
}
return [super forwardingTargetForSelector:aSelector];
}
可以看到,雖然當前 person 沒有實現(xiàn) fun
方法,+resolveInstanceMethod:
也沒有添加其他函數實現(xiàn)。但是我們通過 forwardingTargetForSelector
把當前 person
的方法轉發(fā)給了 friends
對象去執(zhí)行了。打印結果也證明我們成功實現(xiàn)了轉發(fā)。
我們通過 forwardingTargetForSelector
可以修改消息的接收者,該方法返回參數是一個對象,如果這個對象是不是 nil
,也不是 self
,系統(tǒng)會將運行的消息轉發(fā)給這個對象執(zhí)行。否則,繼續(xù)進行下一步:消息重定向流程。
消息重定向
如果經過消息動態(tài)解析、消息接受者重定向,Runtime 系統(tǒng)還是找不到相應的方法實現(xiàn)而無法響應消息,Runtime 系統(tǒng)會利用 -methodSignatureForSelector:
或者 +methodSignatureForSelector:
方法獲取函數的參數和返回值類型。
- 如果
methodSignatureForSelector:
返回了一個NSMethodSignature
對象(函數簽名),Runtime 系統(tǒng)就會創(chuàng)建一個NSInvocation
對象,并通過forwardInvocation:
消息通知當前對象,給予此次消息發(fā)送最后一次尋找 IMP 的機會。 - 如果
methodSignatureForSelector:
返回nil
。則 Runtime 系統(tǒng)會發(fā)出doesNotRecognizeSelector:
消息,程序也就崩潰了。
所以我們可以在 forwardInvocation:
方法中對消息進行轉發(fā)。
注意:類方法和對象方法消息轉發(fā)第三步調用的方法同樣不一樣。
類方法調用的是:
+ methodSignatureForSelector:
+ forwardInvocation:
+ doesNotRecognizeSelector:
對象方法調用的是:
- methodSignatureForSelector:
- forwardInvocation:
- doesNotRecognizeSelector:
用到的方法:
// 獲取類方法函數的參數和返回值類型,返回簽名
+ (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector;
// 類方法消息重定向
+ (void)forwardInvocation:(NSInvocation *)anInvocation;
// 獲取對象方法函數的參數和返回值類型,返回簽名
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector;
// 對象方法消息重定向
- (void)forwardInvocation:(NSInvocation *)anInvocation;
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
if (aSelector == @selector(way)) {
return [NSMethodSignature methodSignatureForSelector:@selector(way)];
}
return [super methodSignatureForSelector:aSelector];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation{
SEL sel = anInvocation.selector;
Friends *f = [[Friends alloc] init];
if([f respondsToSelector:sel]) { // 判斷 Person 對象方法是否可以響應 sel
[anInvocation invokeWithTarget:f]; // 若可以響應,則將消息轉發(fā)給其他對象處理
} else {
[self doesNotRecognizeSelector:sel]; // 若仍然無法響應,則報錯:找不到響應方法
}
}
既然 -forwardingTargetForSelector:
和 -forwardInvocation:
都可以將消息轉發(fā)給其他對象處理,那么兩者的區(qū)別在哪?
區(qū)別就在于 -forwardingTargetForSelector:
只能將消息轉發(fā)給一個對象。而 -forwardInvocation:
可以將消息轉發(fā)給多個對象。
以上就是 Runtime 消息轉發(fā)的整個流程。
消息發(fā)送以及轉發(fā)機制總結
調用 [receiver selector];
后,進行的流程:
-
編譯階段:
[receiver selector];
方法被編譯器轉換為:-
objc_msgSend(receiver,selector)
(不帶參數) -
objc_msgSend(recevier,selector,org1,org2,…)
(帶參數)
-
-
運行時階段:消息接受者
recevier
尋找對應的selector
- 通過
recevier
的isa 指針
找到recevier
的class(類)
; - 在
Class(類)
的cache(方法緩存)
的散列表中尋找對應的IMP(方法實現(xiàn))
; - 如果在
cache(方法緩存)
中沒有找到對應的IMP(方法實現(xiàn))
的話,就繼續(xù)在Class(類)
的method list(方法列表)
中找對應的selector
,如果找到,填充到cache(方法緩存)
中,并返回selector
; - 如果在
class(類)
中沒有找到這個selector
,就繼續(xù)在它的superclass(父類)
中尋找; - 一旦找到對應的
selector
,直接執(zhí)行recevier
對應selector
方法實現(xiàn)的IMP(方法實現(xiàn))
。 - 若找不到對應的
selector
,Runtime 系統(tǒng)進入消息轉發(fā)機制。
- 通過
-
運行時消息轉發(fā)階段:
- 動態(tài)解析:通過重寫
+resolveInstanceMethod:
或者+resolveClassMethod:
方法,利用class_addMethod
方法添加其他函數實現(xiàn); - 消息接受者重定向:如果上一步添加其他函數實現(xiàn),可在當前對象中利用
forwardingTargetForSelector:
方法將消息的接受者轉發(fā)給其他對象; - 消息重定向:如果上一步沒有返回值為
nil
,則利用methodSignatureForSelector:
方法獲取函數的參數和返回值類型。- 如果
methodSignatureForSelector:
返回了一個NSMethodSignature
對象(函數簽名),Runtime 系統(tǒng)就會創(chuàng)建一個NSInvocation
對象,并通過forwardInvocation:
消息通知當前對象,給予此次消息發(fā)送最后一次尋找 IMP 的機會。 - 如果
methodSignatureForSelector:
返回nil
。則 Runtime 系統(tǒng)會發(fā)出doesNotRecognizeSelector:
消息,程序也就崩潰了。
- 如果
- 動態(tài)解析:通過重寫