剛剛接觸到,猶如窺探到一座寶藏,深深被他所以吸引.感覺能用它做很多牛逼的事,但是具體能做什么卻又不知道. 可能這就是接觸新東西所帶給我的體驗(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ì)