如何實現 iOS 中的 Associated Object

發表於2016-08-03

關注倉庫,及時獲得更新:iOS-Source-Code-Analyze

Follow: Draveness · Github

這一篇文章是對 FBRetainCycleDetector 中實現的關聯物件機制的分析;因為追蹤的需要, FBRetainCycleDetector 重新實現了關聯物件,本文主要就是對其實現關聯物件的方法進行分析。

文章中涉及的類主要就是 FBAssociationManager

FBAssociationManager is a tracker of object associations. For given object it can return all objects that are being retained by this object with objc_setAssociatedObject & retain policy.

FBRetainCycleDetector 在對關聯物件進行追蹤時,修改了底層處理關聯物件的兩個 C 函式,objc_setAssociatedObjectobjc_removeAssociatedObjects,在這裡不會分析它是如何修改底層 C 語言函式實現的,如果想要了解相關的內容,可以閱讀下面的文章。

關於如何動態修改 C 語言函式實現可以看動態修改 C 語言函式的實現這篇文章,使用的第三方框架是 fishhook

FBAssociationManager

FBAssociationManager 的類方法 + hook 呼叫時,fishhook 會修改 objc_setAssociatedObjectobjc_removeAssociatedObjects 方法:

將它們的實現替換為 FB::AssociationManager:: fb_objc_setAssociatedObject 以及 FB::AssociationManager::fb_objc_removeAssociatedObjects 這兩個 Cpp 靜態方法。

上面的兩個方法實現都位於 FB::AssociationManager 的名稱空間中:

名稱空間中有兩個用於儲存關聯物件的資料結構:

  • AssociationMap 用於儲存從物件到 ObjectAssociationSet * 指標的對映
  • ObjectAssociationSet 用於儲存某物件所有關聯物件的集合

其中還有幾個比較重要的成員變數:

  • _associationMap 就是 AssociationMap 的例項,是一個用於儲存所有關聯物件的資料結構
  • _associationMutex 用於在修改關聯物件時加鎖,防止出現執行緒競爭等問題,導致不可預知的情況發生
  • hookMutex 以及 hookTaken 都是在類方法 + hook 呼叫時使用的,用於保證 hook 只會執行一次並保證執行緒安全

用於追蹤關聯物件的靜態方法 fb_objc_setAssociatedObject 只會追蹤強引用:

std::lock_guard l(*_associationMutex)fb_objc_setAssociatedObject 過程加鎖,防止死鎖問題,不過 _associationMutex 會在作用域之外被釋放。

通過輸入的 policy 我們可以判斷哪些是強引用物件,然後呼叫 _threadUnsafeSetStrongAssociation 追蹤它們,如果不是強引用物件,通過 _threadUnsafeResetAssociationAtKeykey 對應的 value 刪除,保證追蹤的正確性:

_threadUnsafeSetStrongAssociation 會以 object 作為鍵,查詢或者建立一個 ObjectAssociationSet * 集合,將新的 key 插入到集合中,當然,如果 value == nil 或者上面 fb_objc_setAssociatedObject 方法中傳入的 policy 是非 retain 的就會呼叫 _threadUnsafeResetAssociationAtKey 重置 ObjectAssociationSet 中的關聯物件:

同樣在查詢到對應的 ObjectAssociationSet 之後會擦除 key 對應的值,_threadUnsafeRemoveAssociations 的實現與這個方法也差不多,相較於 reset 方法移除某一個物件的所有關聯物件,該方法僅僅移除了某一個 key 對應的值。

呼叫 _threadUnsafeRemoveAssociations 的方法 fb_objc_removeAssociatedObjects 的實現也很簡單,利用了上面的方法,並在執行結束後,使用原 obj_removeAssociatedObjects 方法對應的函式指標 fb_orig_objc_removeAssociatedObjects 移除關聯物件:

FBObjectiveCGraphElement 獲取關聯物件

因為在獲取某一個物件持有的所有強引用時,不可避免地需要獲取其強引用的關聯物件;因此我們也就需要使用 FBAssociationManager 提供的 + associationsForObject: 介面獲取所有強引用關聯物件:

這個介面呼叫我們在上一節中介紹的 _associationMap,最後得到某一個物件的所有關聯物件的強引用:

這部分的程式碼沒什麼好解釋的,遍歷所有的 key,檢測是否真的存在關聯物件,然後加入可變陣列,最後返回。

總結

FBRetainCycleDetector 為了追蹤某一 NSObject 對關聯物件的引用,重新實現了關聯物件模組,不過其實現與 ObjC 執行時中對關聯物件的實現其實所差無幾,如果對執行時中的關聯物件實現原理有興趣的話,可以看關聯物件 AssociatedObject 完全解析這篇文章,它介紹了底層執行時中的關聯物件的實現。

這是 FBRetainCycleDetector 系列文章中的第三篇,第四篇也是最後一篇文章會介紹 FBRetainCycleDetector 是如何獲取 block 持有的強引用的,這也是我覺得整個框架中實現最精彩的一部分。

關注倉庫,及時獲得更新:iOS-Source-Code-Analyze

Follow: Draveness · Github

原文連結: http://draveness.me/retain-cycle3

相關文章