前面將類的加載說完了,下面說幾個問題,1.方法排序到底是怎樣排序的。2.咱們知道dyld將類會編譯成Macho文件,咱們在探究類的加載的時候,會看到cls->data()方法,這個獲得的就是class_ro_t類型的方法列表,那何時它會編譯成class_ro_t類型的呢(也就是格式怎麼來的)3.類擴展分析。4.關聯對象在底層死怎麼實現的?
下面咱們帶着這些問題進行探索緩存
咱們前面講到fixupMethodList是排序方法,咱們看下方法實現 咱們是根據1215行的sort來進行判斷的,SortBySELAddress這個方法是用方法地址進行排序的。我看下在排序前的方法列表打印 發現此時是無序的,下面是排序後的方法打印 markdown
咱們看到方法名的地址是從小到大進行排列的。這裏不是經過imp排列的。數據結構
咱們看下SortBySELAddress方法 咱們發現SortBySELAddress是跟name相關聯的,在fixupMethodList方法裏,只有sel_registerNameNoLock是跟name相關聯的。下面咱們看下sel_registerNameNoLock方法實現 多線程
紅框內的方法基本不走,除非進行sel的alloc開闢空間。函數
咱們看下search_builtins方法的具體實現 關鍵信息就是_dyld_get_objc_selector,這個方法就是在dyld源碼裏了,咱們去dyld源碼看看 post
若是當前的類存在就去共享緩存去拿,若是是dyld3就在執行一次。這是由於系統的方法會在共享緩存內,咱們本身寫的方法是不在共享緩存內的,因此纔會有若是存在就去共享緩存中去拿。因此咱們本身寫的方法就必須走dyld3。ui
咱們繼續向下研究 atom
1362-1364行就是判斷是否爲空地址,若是爲空就返回。若是不是就拿到imageAndOffset,而target是從selName拿到的,而後賦值給imageAndOffset.raw。1731行就是當前段的首地址進行偏移。因此這個值是不斷的變化的。spa
經過上面能夠知道方法排序顯示同名方法排序,然後是按照方法名的地址大小進行排序的。線程
先看下class_ro_t的結構
700-715行都是屬性變量,在咱們研究過程成基本上是不看的,咱們知道class_ro_t存在Macho中,在編譯期就處理完畢了,這意味着有個方法在編譯期就能讀到class_ro_t。那麼這個時候就須要去看LLVM源碼
這就是LLVM源碼中的class_ro_t結構,咱們發現read方法,下面看下read方法實現。
131-142行就是肯定size大小,大小包含下面這些,後面都有註釋解釋。147行就是從內存讀取process,讀取完成後,來到157-170行就是對class_ro_t的屬性進行賦值。那何時調用的呢?咱們繼續向上看還會發現class_rw_t,class_rw_ext_t其實這些都至關於模板。
咱們繼續向下找的時候找到Read_class_row方法,發現這個方法裏存在class_ro->Read下圖紅框所示 那麼何時調用Read_class_row?
咱們看到上面的兩個方法紅框名字相同,上面是調用地方,最後一個方法就是若是初始化ClassDescriptorV2,就會調用Read_class_row也就是完成class_ro_t。
上面的文章OC底層原理之-類的加載過程-下( 類及分類加載)主要講了分類的加載,提到分類咱們就會想到類的擴展
1.category:類別,分類
2.extension:類擴展
咱們準備代碼
@interface Man : NSObject
- (void)instanceMethod;
- (void)classMethod;
@end
@interface Man ()
@property (nonatomic, copy) NSString *ext_name;
- (void)ext_instanceMethod;
- (void)ext_classMethod;
@end
@implementation Man
- (void)ext_instanceMethod{
}
- (void)ext_classMethod{
}
- (void)instanceMethod{
}
- (void)classMethod{
}
@end
複製代碼
類的擴展必定是在聲明以後,實現以前。其實咱們是常常用的,類的.h就是聲明,.m就是實現和擴展
。 咱們將main.m文件轉成.cpp文件,看下C++的實現。咱們先看屬性ext_name的實現,帶下劃線的ext_name,並放到Man_IMP中 咱們再看下有沒有setter和getter方法 發現是存在的,下面咱們再看下方法,以ext_instanceMethod來看
咱們看到這個想起來cache_t,在編譯器就編譯成這樣的,咱們看到instanceMethod聲明的方法。
只有實現了纔會在method_list_t中
。
下面咱們從新準備下代碼
// Person.m中
@implementation Person
+ (void)load{
}
- (void)kc_instanceMethod2{
NSLog(@"%s",__func__);
}
- (void)kc_instanceMethod1{
NSLog(@"%s",__func__);
}
- (void)kc_instanceMethod3{
NSLog(@"%s",__func__);
}
+ (void)kc_sayClassMethod{
NSLog(@"%s",__func__);
}
- (void)ext_instanceMethod{
}
- (void)ext_classMethod{
}
@end
// Person+PEXT.h中
@interface Person ()
@property (nonatomic, copy) NSString *ext_name;
- (void)ext_instanceMethod;
- (void)ext_classMethod;
@end
複製代碼
咱們在readClass中打斷點,運行,讀取當前Person的方法列表 咱們看到類擴展方法已經加載到方法列表中去了。 類的擴展和分類不一樣,類的擴展不會經過attachCategories進行加載,是直接加載到類方法列表裏。
準備代碼
@implementation Person (A)
- (void)cate_instanceMethod1{
NSLog(@"%s",__func__);
}
- (void)cate_instanceMethod3{
NSLog(@"%s",__func__);
}
- (void)cate_instanceMethod2{
NSLog(@"%s",__func__);
}
+ (void)cate_sayClassMethod{
NSLog(@"%s",__func__);
}
- (void)setCate_name:(NSString *)cate_name{
objc_setAssociatedObject(self, "cate_name", cate_name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (NSString *)cate_name{
return objc_getAssociatedObject(self, "cate_name");
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *person = [Person alloc];
person.cate_name = @"6666";
[person ext_instanceMethod];
NSLog(@"%p",person);
}
return 0;
}
複製代碼
咱們在分類裏添加屬性,在main函數裏進行賦值,若是不進行關聯對象,就會報錯,咱們進行關聯對象,賦值後確定會進入objc_setAssociatedObject方法。咱們看下objc_setAssociatedObject方法,第一個參數爲對象,第二個參數是標識符,第三個參數是Value值,第四個參數是策略。
咱們來看下objc_setAssociatedObject實現 這個至關於接口模式,暴露給外面的永遠不變,內部進行處理,咱們看下SetAssocHook方法實現 當咱們走到SetAssocHook方法的時候,必然會走到_base_objc_setAssociatedObject方法,咱們打斷點 發現確實走到這個方法裏了,下面咱們再看下_object_set_associative_reference方法實現
咱們看169行就是包裝對象,DisguisedPtr是個類型,ObjcAssociation也是對policy - value包裝。
咱們看下acquireValue方法 若是是retain類型就調用objc_retain,若是是copy類型就發送copy消息。也就是對咱們的策略類型進行處理,下面177行AssociationsManager方法,咱們看下實現 其中112-113行是個析構函數這個函數參數是鎖,之因此加鎖是由於防止多線程重複建立,但不是不能建立。下面咱們咱們模擬下這個析構函數 運行代碼
發現至關因而做用域。 回到_object_set_associative_reference方法,咱們能夠發現AssociationsManager能夠有多個。下面就到了AssociationsHashMap,這個是HashMap這是個哈希表,這個是惟一的。由於AssociationsManager方法裏看到AssociationsHashMap是經過_mapStorage.get()方法得到,而_mapStorage是經過static聲明的,是靜態變量,也就是AssociationsHashMap是經過靜態變量_mapStorage獲取的,因此是全場惟一的。
下面咱們運行看下數據結構
咱們看到association打印的value就是咱們的賦值,繼續運行再打印
咱們看到獲取的是空,是由於尚未查找到相應的遞歸查找位域
繼續往下走,來到183行,咱們看下refs_result類型 咱們看到類型很長,咱們該怎麼處理呢?咱們將它複製出來, 咱們經過<和>對應關係,將這個類型分開,其中122-130行是一個參數,第二個參數是個bool類型。咱們回到_object_set_associative_reference方法 判斷refs_result的第二個參數,就是bool類型,判斷就是若是是第一次進來就會走185行方法。咱們無論refs_result第一個一大堆的東西,咱們只管bool值,下面咱們看下try_emplace方法實現
咱們只須要看下make_pair,268-275行:拿到給到的TheBucket(值)和key(關聯對象)去查找,若是找到了就直接返回,若是找不到就將TheBucket和key插入進去,並返回。
咱們回到_object_set_associative_reference方法,看下setHasAssociatedObjects實現 設置標記爲,對isa的has_assoc設置爲true。咱們看下若是value爲空值的操做 作的操做就是刪除。也就是當咱們賦值爲空的時候,它將其刪掉,移除。 下面咱們再來看看try_emplace查找TheBucket怎麼找的,咱們看下LookupBucketFor實現 咱們發現兩個方法同樣,下面的方法是個重載函數,701行調用的上面的方法。爲何呢?咱們看下兩個方法的參數,發現第一個方法FoundBucket帶有const,而另外一個沒有,咱們再看看下701行調用參數是ConstFoundBucket,而ConstFoundBucket在699行用const修飾了,外界調用的是下面的方法,咱們上面也看到TheBucket是沒有用const修飾的
。第二個方法700行是查找TheBucket,若是查找到了,就給FoundBucket,回調出去。咱們看下上面的方法實現
總覽整個方法就是TheBucket查找過程,這個過程和cache_t的方法查找類似,都是循環查找,找不到平移知道找到或者查完爲止。
回到try_emplace方法,繼續往下走找到TheBucket,咱們打印下TheBucket 下面的類型是否是有些相似,這個其實跟refs_result的最後一個長的相同。說明將TheBucket藏在這裏。 繼續到try_emplace方法,若是找到這個TheBucket就會將TheBucket經過make_pair插入值。而後返回true。到此結束。 畫了張流程圖關聯對象的
上面講了文章開頭說的幾個問題。重點是關聯對象,這裏咱們在對關聯對象進行總結:
關聯對象:設值流程
關聯對象插入空流程
關聯對象:取值流程