iOS runtime之消息機制

我們都知道在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如圖
selector驗證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é)

下圖能夠更清晰明確的展示一個消息的整個處理過程:
消息轉(zhuǎn)發(fā)流程
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 轉(zhuǎn)至元數(shù)據(jù)結(jié)尾創(chuàng)建: 董瀟偉,最新修改于: 十二月 23, 2016 轉(zhuǎn)至元數(shù)據(jù)起始第一章:isa和Class一....
    40c0490e5268閱讀 2,041評論 0 9
  • 這篇文章完全是基于南峰子老師博客的轉(zhuǎn)載 這篇文章完全是基于南峰子老師博客的轉(zhuǎn)載 這篇文章完全是基于南峰子老師博客的...
    西木閱讀 30,881評論 33 466
  • 我們常常會聽說 Objective-C 是一門動態(tài)語言,那么這個「動態(tài)」表現(xiàn)在哪呢?我想最主要的表現(xiàn)就是 Obje...
    Ethan_Struggle閱讀 2,326評論 0 7
  • 本文轉(zhuǎn)載自:http://southpeak.github.io/2014/11/03/objective-c-r...
    idiot_lin閱讀 417評論 0 0
  • 原文出處:南峰子的技術(shù)博客 前面我們討論了Runtime中對類和對象的處理,及對成員變量與屬性的處理。這一章,我們...
    _燴面_閱讀 377評論 0 0

友情鏈接更多精彩內(nèi)容