OC底層原理之-類加載部分問題&關聯對象底層原理

前面將類的加載說完了,下面說幾個問題,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格式怎麼來的

先看下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:類別,分類

  • 專門給類添加新的方法
  • 不能給類加成員屬性、添加成員變量,也沒法取到
  • 注意:能夠經過runtime給分類添加屬性
  • 分類中用@property定義變量,只會生成變量的getter,setter方法的聲明,不能生成方法的實現和帶下劃線的成員變量。`(在分類用@property聲明屬性,用本類可以賦值,就是由於生成了getter,setter聲明,可是因爲沒有實現,因此會崩)

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。到此結束。 畫了張流程圖關聯對象的

總結

上面講了文章開頭說的幾個問題。重點是關聯對象,這裏咱們在對關聯對象進行總結:

關聯對象:設值流程

  • 1.建立一個 AssociationsManager 管理類
  • 2.獲取惟一的全局靜態哈希Map
  • 3.判斷是否插入的關聯值是否存在:
    • 存在走第4步
    • 不存在就走 : 關聯對象插入空流程
  • 4.建立一個空的 ObjectAssociationMap 去取查詢的鍵值對
  • 5.若是發現沒有這個 key 就插入一個 空的 BucketT進去 返回
  • 6.標記對象存在關聯對象
  • 7.用當前 修飾策略 和 值 組成了一個 ObjcAssociation 替換原來 BucketT 中的空
  • 8.標記一下 ObjectAssociationMap 的第一次爲 false

關聯對象插入空流程

  • 1.根據 DisguisedPtr 找到 AssociationsHashMap 中的 iterator 迭代查詢器
  • 2.清理迭代器
  • 3.其實若是插入空置 至關於清除

關聯對象:取值流程

  • 1.建立一個 AssociationsManager 管理類
  • 2.獲取惟一的全局靜態哈希Map
  • 3.根據 DisguisedPtr 找到 AssociationsHashMap 中的 iterator 迭代查詢器
  • 4.若是這個迭代查詢器不是最後一個 獲取 : ObjectAssociationMap (這裏有策略和value)
  • 5.找到ObjectAssociationMap的迭代查詢器獲取一個通過屬性修飾符修飾的value
  • 6.返回_value
相關文章
相關標籤/搜索