我們都知道在OC中調(diào)用一個對象的方法會被轉(zhuǎn)化成給一個對象發(fā)送消息。如以下的代碼調(diào)用:
[target doSomeThing:@"param"];
將會被轉(zhuǎn)化成下面這樣的C函數(shù)調(diào)用形式:
objc_msgSend(target, @selector(doSomeThing:), @"param");
那么之后呢,OC是如何處理這個消息的呢?而doSomeThing這個函數(shù)的真正實現(xiàn)是在什么時候得到執(zhí)行的呢?先來熟悉幾個有關(guān)的概念。
Method
先來看一下Method在runtime中的具體定義:
typedef struct objc_method* Method;
struct objc_method
{
SEL method_name;
char* method_types;
IMP method_imp;
}
method_name的類型為SEL,它的具體作用后面會講到。
method_types存儲方法的參數(shù)類型與返回類型,參見下篇文章。
method_imp指向該方法的具體實現(xiàn),本質(zhì)上來說它就是一個函數(shù)指針。
SEL
SEL又叫做方法選擇器,它的具體定義如下:
typedef struct objc_selector* SEL;
可惜我們在源碼中不能直接找出objc_selector的具體定義。但我們猜測它本質(zhì)上就是一個char*指針,可以通過如下代碼來驗證下:
SEL methodSelector = @selector(doSomeThing:);
const char* name = sel_getName(methodSelector);
NSLog(@"%s", methodSelector);
輸出Log如圖
SEL是標(biāo)識一個方法的惟一ID,它存在的目的主要是加快方法查找的速度。
IMP
IMP的具體定義如下:
typedef id (*IMP)(id, SEL, ...);
學(xué)過C的同學(xué)對這個一定很熟悉,這不就是函數(shù)指針嗎!對,IMP實質(zhì)上就是函數(shù)指針,指向一個OC函數(shù)的具體實現(xiàn)。通過SEL可以找到具體的方法實現(xiàn)IMP,通過IMP就可以實現(xiàn)像調(diào)用C函數(shù)那樣調(diào)用OC函數(shù)了,代碼如下:
//定義一個函數(shù)指針
void (*methodImp)(id, SEL);
//通過方法的SEL獲取方法的實現(xiàn)IMP,并賦值給函數(shù)指針
methodImp = (void(*)(id,SEL))[self methodForSelector:@selector(method)];
//通過函數(shù)指針直接調(diào)用函數(shù)
methodImp(self, @selector(method));
方法解析
上篇文章說到Class類中有個objc_method_list字段,它本質(zhì)上就是以objc_method為元素的可變數(shù)組。那么我們可以就方法查找流程做以下幾點推論:
- 先通過對象的isa指針找到它的class
- 然后在class的method_list中查找方法
- 如果沒有找到,則繼續(xù)它的superclass中查找,依此往復(fù),直到找到該方法為止
- 最后執(zhí)行該方法的IMP實現(xiàn)
這個推論大至是成立的,不過還需要補充的一點是,方法查找過程中,會首先到方法的緩存列表中查找,緩存中找不到后才會到method_list中查找,并且方法執(zhí)行時,會將方法添加到緩存中,這樣后續(xù)該方法再次執(zhí)行時,就可以在緩存中查找到并盡快的得到執(zhí)行了。這樣做的好處是,避免每次方法執(zhí)行時都去method_list中查找,進而提高函數(shù)查詢的效率。緩存的定義代碼如下:
typedef struct objc_cache* Cache;
struct objc_cache
{
unsigned int mask;
unsigned int occupied;
cache_entry buckets[1];
}
typedef struct
{
SEL name;
void* unused;
IMP imp;
}cache_entry;
動態(tài)方法解析與轉(zhuǎn)發(fā)
如果開發(fā)者調(diào)用的是一個末定義的方法,會發(fā)生什么呢?當(dāng)然是拋出一個unrecognized selector send to instance異常了。而在異常招聘之前,runtime提供了三次挽救的機會。
resolveInstanceMethod/resolveClassMethod
這兩個方法都是NSObject類中提供的類方法,定義如下:
+(BOOL)resolveInstanceMethod:(SEL)selector;
+(BOOL)resolveClassMethod:(SEL)selector;
它們的主要功能是讓我們有機會為一個SEL動態(tài)的添加實現(xiàn)的IMP。如果在該方法中添加了實現(xiàn)并返回YES,runtime就會重新啟動一次消息發(fā)送過程,進而使SEL得到正確的執(zhí)行。示例代碼如下:
void method(id target, SEL selector)
{
NSLog(@"method working");
}
+(BOOL)resolveInstanceMethod:(SEL)aSel
{
if(aSel == @selector(method))
{
class_addMethod([self class], aSel, method, "v@:");
return YES;
}
return [super resolveInstanceMethod:aSel];
}
如果該方法返回NO,就會進入下一個挽救程序的過程了。
forwardTargetForSelector:
該方法提供了將該SEL轉(zhuǎn)發(fā)給別的對象執(zhí)行的機會。只要這個方法返回值不是nil或者self,那么就會像該返回值對象發(fā)送消息。示例代碼如下:
-(id)forwardTargetForSelector:(SEL)aSel
{
if(aSel == @selector(method))
{
id newTarget = [[MyObject alloc] init];
return newTarget;
}
return [super forwardTargetForSelector:aSel];
}
methodSignatureForSelector:
如果上面兩步都沒有能夠處理SEL,runtime會通過methodSigntureForSelector方法嘗試獲取本次消息調(diào)用的具體環(huán)境信息,包括消息的參數(shù)與返回值類型。并封裝成NSInvocation對象。我們可以在forwardInvocation方法內(nèi)部對該對象作進一步的處理,并使之能夠成功的完成消息處理。如果末能成功獲取NSInvocation對象,那么程序就會發(fā)送doesNotRecognizeSelector消息拋出unrecognized Selector send to xxx的異常。示例代碼如下:
-(void)forwardInvocation:(NSInvocation*)invocation
{
if([otherTarget respondsToSelector:[invocation selector]])
{
[invocation invokeWithTarget:otherTarget];
}
}
總結(jié)
下圖能夠更清晰明確的展示一個消息的整個處理過程: