重溫Runtime

剛剛接觸到,猶如窺探到一座寶藏,深深被他所以吸引.感覺能用它做很多牛逼的事,但是具體能做什么卻又不知道. 可能這就是接觸新東西所帶給我的體驗(yàn),直覺告訴它確實(shí)有無限的創(chuàng)造力.

Objective-C 調(diào)用方法,每天被用到無數(shù)次,寫了無數(shù)遍的alloc init,從來沒有想過這個到底怎么調(diào)用.今天撐著興致高漲,做點(diǎn)學(xué)習(xí)筆記.

首先,OC調(diào)用一個類的方法,并不是把這個方法編譯成一個唯一的符號,然后調(diào)用的時(shí)候查找符號表. OC在runtime層會走:voidobjc_msgSend(void/* id self, SEL op, ... */) 這樣一個方法.如注釋所寫,參數(shù)分別是方法調(diào)用者,方法選擇器,方法參數(shù). 那么msgSend又是如何做的分發(fā)呢,查詢源碼注釋可知:

判斷receiver是否為nil,也就是objc_msgSend的第一個參數(shù)self,也就是要調(diào)用的那個方法所屬對象從緩存里尋找,找到了則分發(fā),否則利用objc-class.mm中_class_lookupMethodAndLoadCache3方法去尋找selector

從本class的method list尋找selector,如果找到,填充到緩存中,并返回selector,否則尋找父類的method list,并依次往上尋找,直到找到selector,填充到緩存中,并返回selector,否則調(diào)用_class_resolveMethod,如果可以動態(tài)resolve為一個selector,不緩存,方法返回,否則轉(zhuǎn)發(fā)這個selector

else 拋出異常.

簡單總結(jié);

首先,通過 obj 的 isa 指針找到它的 class ;

在 class 的 method list 找 foo ;

如果 class 中沒到 foo,繼續(xù)往它的 superclass 中找 ;

一旦找到 foo 這個函數(shù),就去執(zhí)行它的實(shí)現(xiàn)IMP


我們在實(shí)際調(diào)用中不難發(fā)現(xiàn),當(dāng)我們用一個下層類去調(diào)用上層類的每一個方法時(shí)如果沒有緩存,那么整個查找鏈?zhǔn)窍喈?dāng)長的.就算方法是在這個類里面,當(dāng)方法比較多的時(shí)候,每次都查找也是費(fèi)事費(fèi)力的一件事情.當(dāng)一個方法重復(fù)被調(diào)用,哪怕只重復(fù)一次,那做緩存都是相當(dāng)有必要的.


讓我們來看一下objc_cache到底是什么:

struct objc_cache {

uintptr_t mask;

uintprt_t occupied;

cache_entry *buckets[1];

}

objc_cache的定義看起來很簡單,它包含了下面三個變量:

1)、mask:可以認(rèn)為是當(dāng)前能達(dá)到的最大index(從0開始的),所以緩存的size(total)是mask+1

2)、occupied:被占用的槽位,因?yàn)榫彺媸且陨⒘斜淼男问酱嬖诘?,所以會有空槽,而occupied表示當(dāng)前被占用的數(shù)目

3)、buckets:用數(shù)組表示的hash表,cache_entry類型,每一個cache_entry代表一個方法緩存

(buckets定義在objc_cache的最后,說明這是一個可變長度的數(shù)組)


cache_entry定義也包含了三個字段,分別是:

typedef struct {

SEL name;

void *unused;

IMP imp;

}

1)、name,被緩存的方法名字

2)、unused,保留字段,還沒被使用。

3)、imp,方法實(shí)現(xiàn)

緩存的存儲使用了散列表。因?yàn)樯⒘斜頇z索起來更快,sel被散列后找到一個空槽放在buckets中.

Class 類的緩存存在metaclass中, 所以每個類都只有一份方法緩存,而不是每一個類的object都保存一份. 從父類取到的方法,也會存在類本身的方法緩存里。而當(dāng)用一個父類對象去調(diào)用那個方法的時(shí)候,也會在父類的metaclass里緩存一份。

簡單總結(jié):

這也就是objc_class中另一個重要成員objc_cache做的事情 - 再找到 foo 之后,把 foo 的method_name作為 key ,method_imp作為 value 給存起來。當(dāng)再次收到 foo 消息的時(shí)候,可以直接在 cache 里找到,避免去遍歷objc_method_list.


類方法表是list 有序的,這就是為什么我們在寫類別的時(shí)候,最好是不要重寫系統(tǒng)的方法,其實(shí)并不是覆蓋掉了系統(tǒng)原有的方法,只是通過runtime增加了一個新的方法,但是這個方法會排在系統(tǒng)原有方法的前面,所以調(diào)用的時(shí)候,由于list有序,所以優(yōu)先調(diào)用了重寫的方法.

我們應(yīng)該如何調(diào)用被"被覆蓋的方法"呢

Class currentClass = [MyClass Class];

Class currentClass = [[MVVMalloc]init];

MVVM*my = [[MVVMalloc]init];

if(currentClass) {

unsignedintcount;

Method*methodList =class_copyMethodList(currentClass, &count);

IMPlastimp =nil;

SELlastsel =nil;

for(NSIntegeri =0; i

Methodmethod = methodList[i];

NSString*methodName = [NSStringstringWithCString:sel_getName(method)encoding:NSUTF8StringEncoding];

if([methodNameisEqualToString:@"方法名"]) {

lastimp =method_getImplementation(method);

lastsel =method_getName(method);

}

}

typedefvoid(*fn)(id,SEL);

if(lastimp !=NULL) {

fnf = (fn)lastimp;

f(my,lastsel);

}

free(methodList);

}


當(dāng)你有一個類有兩個類別,并且擁有同一方法的時(shí)候,可以通過控制編譯順序選擇優(yōu)先加載那個方法.(Compile Sources 里的文件順序)

學(xué)習(xí)源自--美團(tuán)技術(shù)技術(shù)團(tuán)隊(duì)博客, GLOW技術(shù)團(tuán)隊(duì)

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

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

  • 轉(zhuǎn)至元數(shù)據(jù)結(jié)尾創(chuàng)建: 董瀟偉,最新修改于: 十二月 23, 2016 轉(zhuǎn)至元數(shù)據(jù)起始第一章:isa和Class一....
    40c0490e5268閱讀 2,051評論 0 9
  • 我們常常會聽說 Objective-C 是一門動態(tài)語言,那么這個「動態(tài)」表現(xiàn)在哪呢?我想最主要的表現(xiàn)就是 Obje...
    Ethan_Struggle閱讀 2,333評論 0 7
  • 轉(zhuǎn)載:http://yulingtianxia.com/blog/2014/11/05/objective-c-r...
    F麥子閱讀 832評論 0 2
  • 這篇文章完全是基于南峰子老師博客的轉(zhuǎn)載 這篇文章完全是基于南峰子老師博客的轉(zhuǎn)載 這篇文章完全是基于南峰子老師博客的...
    西木閱讀 30,886評論 33 466
  • 今天終于等到了向心理咨詢師高高老師的語音咨詢,這是有生以來第一次和心理咨詢師正面接觸,雖然就要只有一個小時(shí),但是一...
    珍惜年青閱讀 205評論 1 1

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