天天看點

iOS-底層原理 19:類擴充 與 關聯對象 底層原理探索

iOS 底層原理 文章彙總

本文的主要目的是針對類的加載的一個擴充,主要講講類拓展和分類的底層實作原理

【面試題】類擴充 與 分類 的差別

1、category 類别、分類

  • 專門用來給類添加新的方法

  • 不能給類添加成員屬性

    ,添加了成員屬性,也無法取到
  • 注意:其實

    可以通過runtime 給分類添加屬性

    ,即屬性關聯,重寫setter、getter方法
  • 分類中用

    @property

    定義變量,

    隻會生成

    變量的

    setter、getter

    方法的

    聲明

    不能生成方法實作 和 帶下劃線的成員變量

2、extension 類擴充

  • 可以說成是

    特殊的分類

    ,也可稱作

    匿名分類

  • 可以

    給類添加成員屬性

    ,但

    是是私有變量

  • 可以

    給類添加方法

    ,也

    是私有方法

類擴充 底層原理探索

類的擴充有兩種建立方式

  • 直接在

    類中書寫

    :永遠

    在聲明之後,在實作之前

    (需要在

    .m檔案

    中書寫)
  • 通過 command+N 建立 -> Objective-C File -> 選擇

    Extension

    iOS-底層原理 19:類擴充 與 關聯對象 底層原理探索

類擴充的的本質

通過clang底層編譯

  • 寫一個類擴充
    iOS-底層原理 19:類擴充 與 關聯對象 底層原理探索
  • 通過

    clang -rewrite-objc main.mm -o main.cpp

    指令生成cpp檔案,打開cpp檔案,搜尋

    ext_name

    屬性
    iOS-底層原理 19:類擴充 與 關聯對象 底層原理探索
  • 檢視 LGTeacher 類拓展的方法,在

    編譯過程

    中,方法就直接添加到了

    methodlist

    中,作為類的一部分,即

    編譯時期直接添加到本類

    裡面
    iOS-底層原理 19:類擴充 與 關聯對象 底層原理探索

通過源碼調試探索

  • 建立

    LGPerson+LGEXT.h

    即類的擴充,并聲明兩個方法
    iOS-底層原理 19:類擴充 與 關聯對象 底層原理探索
  • LGperon.m

    中實作這兩個方法
    iOS-底層原理 19:類擴充 與 關聯對象 底層原理探索
  • 運作

    objc

    源碼程式,在

    readClass

    中斷住,檢視kc_ro
    iOS-底層原理 19:類擴充 與 關聯對象 底層原理探索
    • p kc_ro->baseMethodList

    • p $0->get(0)

      ~

      p $0->get(10)

      iOS-底層原理 19:類擴充 與 關聯對象 底層原理探索

總結

  • 類的擴充 在編譯器 會作為類的一部分,和類一起編譯進來

  • 類的擴充

    隻是

    聲明

    依賴于目前的主類

    ,沒有.m檔案,可以了解為一個

    ·h檔案

分類關聯對象 底層原理探索

其底層原理的實作,主要分為兩部分:

  • 通過

    objc_setAssociatedObject

    設值流程
  • 通過

    objc_getAssociatedObject

    取值流程

關聯對象-設值流程

  • 分類LG

    中重寫屬性

    cate_name

    set、get

    方法,通過

    runtime

    的屬性關聯方法實作
    iOS-底層原理 19:類擴充 與 關聯對象 底層原理探索
  • 運作程式,斷點斷在

    main

    cate_name

    指派處
    iOS-底層原理 19:類擴充 與 關聯對象 底層原理探索
  • 繼續往下運作,斷在分類的

    setCate_name

    方法中
    iOS-底層原理 19:類擴充 與 關聯對象 底層原理探索
    其中

    objc_setAssociatedObject

    方法有四個參數,分别表示:
    • 參數1:要關聯的對象,即給誰添加關聯屬性
    • 參數2:辨別符,友善下次查找
    • 參數3:value
    • 參數4:屬性的

      政策

      ,即nonatomic、atomic、assign等,如下所示
      iOS-底層原理 19:類擴充 與 關聯對象 底層原理探索
  • 進入

    objc_setAssociatedObject

    源碼實作
    • 這種設計模式屬于是

      接口模式

      ,對外的接口不變,内部的邏輯變化不影響外部的調用, 類似于

      set

      方法的底層源碼實作
      iOS-底層原理 19:類擴充 與 關聯對象 底層原理探索
  • 進入

    get

    方法實作,其中

    ChainedHookFunction

    是一個

    函數指針

    iOS-底層原理 19:類擴充 與 關聯對象 底層原理探索
    • 進入

      SetAssocHook

      ,其底層實作是

      _base_objc_setAssociatedObject

      ,類型是

      ChainedHookFunction

      iOS-底層原理 19:類擴充 與 關聯對象 底層原理探索
      是以可以了解為

      SetAssocHook.get()

      等價于

      _base_objc_setAssociatedObject

void
objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
{
    SetAssocHook.get()(object, key, value, policy);//接口模式,對外接口始終不變
}

👇等價于

void
objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
{
    _base_objc_setAssociatedObject(object, key, value, policy);//接口模式,對外接口始終不變
}
           
  • 進入

    _base_objc_setAssociatedObject

    源碼實作:

    _base_objc_setAssociatedObject -> _object_set_associative_reference

    ,通過斷點調試,确實會來到這裡
    iOS-底層原理 19:類擴充 與 關聯對象 底層原理探索

_object_set_associative_reference 方法

進入

_object_set_associative_reference

源碼實作

關于關聯對象 底層原理的探索 主要是看

value存

到了哪裡, 以及如何

取出value

,以下是源碼

void
_object_set_associative_reference(id object, const void *key, id value, uintptr_t policy)
{
    // This code used to work when nil was passed for object and key. Some code
    // probably relies on that to not crash. Check and handle it explicitly.
    // rdar://problem/44094390
    if (!object && !value) return;

    if (object->getIsa()->forbidsAssociatedObjects())
        _objc_fatal("objc_setAssociatedObject called on instance (%p) of class %s which does not allow associated objects", object, object_getClassName(object));
    //object封裝成一個數組結構類型,類型為DisguisedPtr
    DisguisedPtr<objc_object> disguised{(objc_object *)object};//相當于包裝了一下 對象object,便于使用
    // 包裝一下 policy - value
    ObjcAssociation association{policy, value};

    // retain the new value (if any) outside the lock.
    association.acquireValue();//根據政策類型進行處理
    //局部作用域空間
    {
        //初始化manager變量,相當于自動調用AssociationsManager的析構函數進行初始化
        AssociationsManager manager;//并不是全場唯一,構造函數中加鎖隻是為了避免重複建立,在這裡是可以初始化多個AssociationsManager變量的
    
        AssociationsHashMap &associations(manager.get());//AssociationsHashMap 全場唯一

        if (value) {
            auto refs_result = associations.try_emplace(disguised, ObjectAssociationMap{});//傳回的結果是一個類對
            if (refs_result.second) {//判斷第二個存不存在,即bool值是否為true
                /* it's the first association we make 第一次建立關聯*/
                object->setHasAssociatedObjects();//nonpointerIsa ,标記位true
            }

            /* establish or replace the association 建立或者替換關聯*/
            auto &refs = refs_result.first->second; //得到一個空的桶子,找到引用對象類型,即第一個元素的second值
            auto result = refs.try_emplace(key, std::move(association));//查找目前的key是否有association關聯對象
            if (!result.second) {//如果結果不存在
                association.swap(result.first->second);
            }
        } else {//如果傳的是空值,則移除關聯,相當于移除
            auto refs_it = associations.find(disguised);
            if (refs_it != associations.end()) {
                auto &refs = refs_it->second;
                auto it = refs.find(key);
                if (it != refs.end()) {
                    association.swap(it->second);
                    refs.erase(it);
                    if (refs.size() == 0) {
                        associations.erase(refs_it);

                    }
                }
            }
        }
    }

    // release the old value (outside of the lock).
    association.releaseHeldValue();//釋放
}
           

通過源碼可知,主要分為以下幾部分:

  • 1:建立一個

    AssociationsManager

    管理類
  • 2:擷取

    唯一

    的全局靜态哈希

    Map:AssociationsHashMap

  • 3:判斷是否插入的

    關聯值value

    是否存在
    • 3.1:存在走第4步
    • 3.2:不存在就走 :

      關聯對象-插入空流程

  • 4:通過

    try_emplace

    方法,并建立一個空的

    ObjectAssociationMap

    去取查詢的鍵值對:
  • 5:如果發現

    沒有

    這個

    key

    插入一個 空的 BucketT

    進去并傳回true
  • 6:通過

    setHasAssociatedObjects

    方法

    标記對象存在關聯對象

    即置

    isa指針

    has_assoc

    屬性為

    true

  • 7:用目前

    policy 和 value

    組成了一個

    ObjcAssociation

    替換原來

    BucketT 中的空

  • 8:标記一下

    ObjectAssociationMap

    第一次

    false

設定流程 源碼調試

  • 定義

    AssociationsManager

    類型的變量,相當于自動調用

    AssociationsManager

    的析構函數進行初始化
    • 加鎖lock,并不代表 唯一,隻是為了避免多線程重複建立,其實在外面是可以定義多個

      AssociationsManager manager;

  • 定義

    AssociationsHashMap

    類型的哈希map,這個全場唯一的,從哪裡可以展現呢?
    • 通過

      _mapStorage.get()

      生成哈希map,其中

      _mapStorage

      是一個靜态變量,是以

      哈希map 永遠是通過靜态變量擷取出來

      的,是以是

      全場唯一

      iOS-底層原理 19:類擴充 與 關聯對象 底層原理探索
  • 通過調試,可以檢視 目前的資料結構
    • p disguised

      :其中的value是來自object 還原出來的
    • p association

    • p manager

    • p associations

      :目前的

      associations

      0x0

      ,表示還沒有查找到相應的遞歸查找域中
      iOS-底層原理 19:類擴充 與 關聯對象 底層原理探索
  • 走到局部作用域的if判斷,此時的

    value

    是有值的,為

    KC

    iOS-底層原理 19:類擴充 與 關聯對象 底層原理探索
    • 如果傳入的

      value是空值

      ,走到局部作用域的

      else流程

      ,通過源碼可知,相當于

      移除關聯

      iOS-底層原理 19:類擴充 與 關聯對象 底層原理探索
  • 繼續往下執行,檢視

    refs_result

    – p refs_result,其中的類型資料非常多,可以進行拆解檢視
    • associations

      調用

      try_emplace

      方法,傳入一個對象

      disguised

      和 一個空的關聯map

      ObjectAssociationMap{}

      iOS-底層原理 19:類擴充 與 關聯對象 底層原理探索
//pair -- 表示有鍵值對
(std::__1::pair<
 objc::DenseMapIterator<DisguisedPtr<objc_object>,
 
 objc::DenseMap<const void *, objc::ObjcAssociation, objc::DenseMapValueInfo<objc::ObjcAssociation>, objc::DenseMapInfo<const void *>, objc::detail::DenseMapPair<const void *, objc::ObjcAssociation> >,
 
 objc::DenseMapValueInfo<objc::DenseMap<const void *, objc::ObjcAssociation, objc::DenseMapValueInfo<objc::ObjcAssociation>, objc::DenseMapInfo<const void *>, objc::detail::DenseMapPair<const void *, objc::ObjcAssociation> > >,
 
 objc::DenseMapInfo<DisguisedPtr<objc_object> >,
 
 objc::detail::DenseMapPair<DisguisedPtr<objc_object>, objc::DenseMap<const void *, objc::ObjcAssociation, objc::DenseMapValueInfo<objc::ObjcAssociation>, objc::DenseMapInfo<const void *>, objc::detail::DenseMapPair<const void *, objc::ObjcAssociation> > >,
 
 false>,
 
 bool>)

//可以簡寫為

(std::__1::pair<
 
 objc
 
bool>)
           
  • 進入

    try_emplace

    方法的源碼實作
    • 有兩個傳回,都是通過

      std::make_pair

      生成相應的鍵值對
    • 通過

      LookupBucketFor

      方法

      查找桶子

      ,如果map中已經

      存在

      ,則

      直接傳回

      ,其中

      make_pair

      的第二個參數

      bool值為false

    • 如果沒

      有找到

      ,則通過

      InsertIntoBucket

      插入map,其中

      make_pair

      的第二個參數

      bool值為true

      iOS-底層原理 19:類擴充 與 關聯對象 底層原理探索
  • 進入

    LookupBucketFor

    源碼,有兩個同名方法,其中第二個方法屬于重載函數,差別于第一個的是第二個參數沒有const修飾,通過調試可知,外部的調用是調用的第二個重載函數,而第二個

    LookupBucketFor

    方法,内部的實作是調用第一個

    LookupBucketFor

    方法
    iOS-底層原理 19:類擴充 與 關聯對象 底層原理探索
    • 第一個

      LookupBucketFor

      方法源碼實作
      iOS-底層原理 19:類擴充 與 關聯對象 底層原理探索
    • 第二個

      LookupBucketFor

      方法的源碼實作
      iOS-底層原理 19:類擴充 與 關聯對象 底層原理探索
  • 斷點運作至

    try_emplace

    方法中的擷取bucket部分

    TheBucket = InsertIntoBucket(TheBucket, Key, std::forward<Ts>(Args)...);

    • p TheBucket
      iOS-底層原理 19:類擴充 與 關聯對象 底層原理探索
      其中

      TheBucket

      的類型與

      refs_result

      中屬性的類型是一緻
      iOS-底層原理 19:類擴充 與 關聯對象 底層原理探索
  • 進入

    if (refs_result.second)

    的if流程,通過

    setHasAssociatedObjects

    nonpointerIsa

    has_assoc

    标記為

    true

    iOS-底層原理 19:類擴充 與 關聯對象 底層原理探索
  • 繼續往下執行,檢視

    refs

    • p refs

      ,執行

      try_emplace

      前檢視
    • p refs

      ,執行

      try_emplace

      後檢視
      iOS-底層原理 19:類擴充 與 關聯對象 底層原理探索
      第一次執行

      try_emplace

      插入的是一個空桶,還沒有值,第二次執行第一次執行

      try_emplace

      才插入值,即往空桶中插入

      ObjectAssociationMap(value,policy)

      ,傳回true,可以通過調試驗證
      iOS-底層原理 19:類擴充 與 關聯對象 底層原理探索
  • p result.second

    ,傳回的true,到此就将屬性與value關聯上了
    iOS-底層原理 19:類擴充 與 關聯對象 底層原理探索

是以,關聯對象的設值圖示如下,有點類似于cache_t中的insert方法插入sel-imp的邏輯,如下圖所示

iOS-底層原理 19:類擴充 與 關聯對象 底層原理探索

屬性關聯涉及的哈希map結構

是以到目前為止,關聯屬性涉及的map結構如下

iOS-底層原理 19:類擴充 與 關聯對象 底層原理探索
  • AssociationsManager

    可以有多個,通過

    AssociationsManagerLock

    鎖可以得到一個

    AssociationsHashMap

    類型的map
  • map中有很多的關聯對象map,類型是

    ObjectAssociationMap

    ,其中key為

    DisguisedPtr<objc_object>

    ,例如LGPerson會對應一個

    ObjectAssociationMap

    ,LGTeacher也會對應一個

    ObjectAssociationMap

typedef DenseMap<DisguisedPtr<objc_object>, ObjectAssociationMap> AssociationsHashMap;
           
  • ObjectAssociationMap

    哈希表中有很多

    key-value

    鍵值對,其中

    key

    的類型為

    const void *

    value

    的類型為

    ObjcAssociation

typedef DenseMap<const void *, ObjcAssociation> ObjectAssociationMap;
           
  • 其中

    ObjcAssociation

    是用于包裝policy和value的一個類
    iOS-底層原理 19:類擴充 與 關聯對象 底層原理探索
對象插入空流程

根據源碼可知,主要是局部作用域中的

else流程

,其實這個流程可以通俗的了解為

當傳入的value為nil時,則移除關聯

,主要分為以下幾步:

iOS-底層原理 19:類擴充 與 關聯對象 底層原理探索
  • 1、根據 DisguisedPtr 找到 AssociationsHashMap 中的 iterator 疊代查詢器
  • 2、清理疊代器
  • 3、其實如果插入空置 相當于清除

關聯對象-取值流程

  • main中 列印

    person.cate_name

    的值,斷點來到分類中重寫的屬性

    get

    方法
    iOS-底層原理 19:類擴充 與 關聯對象 底層原理探索
  • 進入

    objc_getAssociatedObject

    源碼實作
    iOS-底層原理 19:類擴充 與 關聯對象 底層原理探索

_object_get_associative_reference方法

其源碼實作如下:

id
_object_get_associative_reference(id object, const void *key)
{
    ObjcAssociation association{};//建立空的關聯對象

    {
        AssociationsManager manager;//建立一個AssociationsManager管理類
        AssociationsHashMap &associations(manager.get());//擷取全局唯一的靜态哈希map
        AssociationsHashMap::iterator i = associations.find((objc_object *)object);//找到疊代器,即擷取buckets
        if (i != associations.end()) {//如果這個疊代查詢器不是最後一個 擷取
            ObjectAssociationMap &refs = i->second; //找到ObjectAssociationMap的疊代查詢器擷取一個經過屬性修飾符修飾的value
            ObjectAssociationMap::iterator j = refs.find(key);//根據key查找ObjectAssociationMap,即擷取bucket
            if (j != refs.end()) {
                association = j->second;//擷取ObjcAssociation
                association.retainReturnedValue();
            }
        }
    }

    return association.autoreleaseReturnedValue();//傳回value
}
           

通過源碼可知,主要分為以下幾部分

  • 1:建立一個

    AssociationsManager

    管理類
  • 2:擷取唯一的全局靜态哈希Map:

    AssociationsHashMap

  • 3:通過

    find

    方法根據

    DisguisedPtr

    找到

    AssociationsHashMap

    中的

    iterator

    疊代查詢器
  • 4:如果這個疊代查詢器不是最後一個 擷取 :

    ObjectAssociationMap (policy和value)

  • 5:通過

    find

    方法找到

    ObjectAssociationMap

    的疊代查詢器擷取一個經過屬性修飾符修飾的

    value

  • 6:傳回

    value

調試取值流程

  • ,接着上一步調試,進入

    _object_get_associative_reference

    源碼實作
    • 進入

      find

      方法:根據關聯對象疊代查找

      AssociationsHashMap

      ,即

      buckets

      iOS-底層原理 19:類擴充 與 關聯對象 底層原理探索
    • p i

    • p i->second

      iOS-底層原理 19:類擴充 與 關聯對象 底層原理探索
  • 再次通過

    find

    方法,在

    buckets

    中查找與key配對的

    bucket

    • find

      方法執行之前,

      j

      的列印,此時的

      value為nil

      iOS-底層原理 19:類擴充 與 關聯對象 底層原理探索
    • find

      方法查詢之後,

      j

      的列印,此時的

      value 為KC

      iOS-底層原理 19:類擴充 與 關聯對象 底層原理探索

總結

是以,綜上所述,是以

關聯對象的底層調用流程

如下圖所示

iOS-底層原理 19:類擴充 與 關聯對象 底層原理探索

總的來說,

關聯對象

主要就是

兩層哈希map的處理

,即存取時都是兩層處理,類似于二維數組

補充

AssociationsHashMap 唯一性驗證

  • 驗證

    AssociationsHashMap

    的唯一性,而

    AssociationsManager

    不唯一
    • 去掉

      AssociationsManager

      中的加鎖
      iOS-底層原理 19:類擴充 與 關聯對象 底層原理探索
    • _object_set_associative_reference

      方法中再次定義一遍

      manager

      associations

      iOS-底層原理 19:類擴充 與 關聯對象 底層原理探索
    • 下面是調試運作的結果,從下圖中可以看出兩個

      association

      的位址是一樣的,驗證了其唯一性
      iOS-底層原理 19:類擴充 與 關聯對象 底層原理探索
      iOS-底層原理 19:類擴充 與 關聯對象 底層原理探索
  • 加鎖的目的:保證

    對象的安全性,防止沖突

AssociationsManager manager;

👇等價于

AssociationsManager();

lock();

...

unlock();//作用域之後unlock

           

繼續閱讀