關注倉庫,及時獲得更新: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_setAssociatedObject
和 objc_removeAssociatedObjects
,在這裡不會分析它是如何修改底層 C 語言函式實現的,如果想要了解相關的內容,可以閱讀下面的文章。
關於如何動態修改 C 語言函式實現可以看動態修改 C 語言函式的實現這篇文章,使用的第三方框架是 fishhook。
FBAssociationManager
在 FBAssociationManager
的類方法 + hook
呼叫時,fishhook 會修改 objc_setAssociatedObject
和 objc_removeAssociatedObjects
方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
+ (void)hook { #if _INTERNAL_RCD_ENABLED std::lock_guard l(*FB::AssociationManager::hookMutex); rcd_rebind_symbols((struct rcd_rebinding[2]){ { "objc_setAssociatedObject", (void *)FB::AssociationManager::fb_objc_setAssociatedObject, (void **)&FB::AssociationManager::fb_orig_objc_setAssociatedObject }, { "objc_removeAssociatedObjects", (void *)FB::AssociationManager::fb_objc_removeAssociatedObjects, (void **)&FB::AssociationManager::fb_orig_objc_removeAssociatedObjects }}, 2); FB::AssociationManager::hookTaken = true; #endif //_INTERNAL_RCD_ENABLED } |
將它們的實現替換為 FB::AssociationManager:: fb_objc_setAssociatedObject
以及 FB::AssociationManager::fb_objc_removeAssociatedObjects
這兩個 Cpp 靜態方法。
上面的兩個方法實現都位於 FB::AssociationManager
的名稱空間中:
1 2 3 4 5 6 7 8 9 10 11 12 |
namespace FB { namespace AssociationManager { using ObjectAssociationSet = std::unordered_set; using AssociationMap = std::unordered_map; static auto _associationMap = new AssociationMap(); static auto _associationMutex = new std::mutex; static std::mutex *hookMutex(new std::mutex); static bool hookTaken = false; ... } |
名稱空間中有兩個用於儲存關聯物件的資料結構:
AssociationMap
用於儲存從物件到ObjectAssociationSet *
指標的對映ObjectAssociationSet
用於儲存某物件所有關聯物件的集合
其中還有幾個比較重要的成員變數:
_associationMap
就是AssociationMap
的例項,是一個用於儲存所有關聯物件的資料結構_associationMutex
用於在修改關聯物件時加鎖,防止出現執行緒競爭等問題,導致不可預知的情況發生hookMutex
以及hookTaken
都是在類方法+ hook
呼叫時使用的,用於保證 hook 只會執行一次並保證執行緒安全
用於追蹤關聯物件的靜態方法 fb_objc_setAssociatedObject
只會追蹤強引用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
static void fb_objc_setAssociatedObject(id object, void *key, id value, objc_AssociationPolicy policy) { { std::lock_guard l(*_associationMutex); if (policy == OBJC_ASSOCIATION_RETAIN || policy == OBJC_ASSOCIATION_RETAIN_NONATOMIC) { _threadUnsafeSetStrongAssociation(object, key, value); } else { // We can change the policy, we need to clear out the key _threadUnsafeResetAssociationAtKey(object, key); } } fb_orig_objc_setAssociatedObject(object, key, value, policy); } |
std::lock_guard l(*_associationMutex)
對 fb_objc_setAssociatedObject
過程加鎖,防止死鎖問題,不過 _associationMutex
會在作用域之外被釋放。
通過輸入的 policy
我們可以判斷哪些是強引用物件,然後呼叫 _threadUnsafeSetStrongAssociation
追蹤它們,如果不是強引用物件,通過 _threadUnsafeResetAssociationAtKey
將 key
對應的 value
刪除,保證追蹤的正確性:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
void _threadUnsafeSetStrongAssociation(id object, void *key, id value) { if (value) { auto i = _associationMap->find(object); ObjectAssociationSet *refs; if (i != _associationMap->end()) { refs = i->second; } else { refs = new ObjectAssociationSet; (*_associationMap)[object] = refs; } refs->insert(key); } else { _threadUnsafeResetAssociationAtKey(object, key); } } |
_threadUnsafeSetStrongAssociation
會以 object 作為鍵,查詢或者建立一個 ObjectAssociationSet *
集合,將新的 key
插入到集合中,當然,如果 value == nil
或者上面 fb_objc_setAssociatedObject
方法中傳入的 policy
是非 retain
的就會呼叫 _threadUnsafeResetAssociationAtKey
重置 ObjectAssociationSet
中的關聯物件:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
void _threadUnsafeResetAssociationAtKey(id object, void *key) { auto i = _associationMap->find(object); if (i == _associationMap->end()) { return; } auto *refs = i->second; auto j = refs->find(key); if (j != refs->end()) { refs->erase(j); } } |
同樣在查詢到對應的 ObjectAssociationSet
之後會擦除 key
對應的值,_threadUnsafeRemoveAssociations
的實現與這個方法也差不多,相較於 reset 方法移除某一個物件的所有關聯物件,該方法僅僅移除了某一個 key
對應的值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
void _threadUnsafeRemoveAssociations(id object) { if (_associationMap->size() == 0 ){ return; } auto i = _associationMap->find(object); if (i == _associationMap->end()) { return; } auto *refs = i->second; delete refs; _associationMap->erase(i); } |
呼叫 _threadUnsafeRemoveAssociations
的方法 fb_objc_removeAssociatedObjects
的實現也很簡單,利用了上面的方法,並在執行結束後,使用原 obj_removeAssociatedObjects
方法對應的函式指標 fb_orig_objc_removeAssociatedObjects
移除關聯物件:
1 2 3 4 5 6 7 8 |
static void fb_objc_removeAssociatedObjects(id object) { { std::lock_guard l(*_associationMutex); _threadUnsafeRemoveAssociations(object); } fb_orig_objc_removeAssociatedObjects(object); } |
FBObjectiveCGraphElement 獲取關聯物件
因為在獲取某一個物件持有的所有強引用時,不可避免地需要獲取其強引用的關聯物件;因此我們也就需要使用 FBAssociationManager
提供的 + associationsForObject:
介面獲取所有強引用關聯物件:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
- (NSSet *)allRetainedObjects { NSArray *retainedObjectsNotWrapped = [FBAssociationManager associationsForObject:_object]; NSMutableSet *retainedObjects = [NSMutableSet new]; for (id obj in retainedObjectsNotWrapped) { FBObjectiveCGraphElement *element = FBWrapObjectGraphElementWithContext(self, obj, _configuration, @[@"__associated_object"]); if (element) { [retainedObjects addObject:element]; } } return retainedObjects; } |
這個介面呼叫我們在上一節中介紹的 _associationMap
,最後得到某一個物件的所有關聯物件的強引用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
+ (NSArray *)associationsForObject:(id)object { return FB::AssociationManager::associations(object); } NSArray *associations(id object) { std::lock_guard l(*_associationMutex); if (_associationMap->size() == 0 ){ return nil; } auto i = _associationMap->find(object); if (i == _associationMap->end()) { return nil; } auto *refs = i->second; NSMutableArray *array = [NSMutableArray array]; for (auto &key: *refs) { id value = objc_getAssociatedObject(object, key); if (value) { [array addObject:value]; } } return array; } |
這部分的程式碼沒什麼好解釋的,遍歷所有的 key
,檢測是否真的存在關聯物件,然後加入可變陣列,最後返回。
總結
FBRetainCycleDetector 為了追蹤某一 NSObject
對關聯物件的引用,重新實現了關聯物件模組,不過其實現與 ObjC 執行時中對關聯物件的實現其實所差無幾,如果對執行時中的關聯物件實現原理有興趣的話,可以看關聯物件 AssociatedObject 完全解析這篇文章,它介紹了底層執行時中的關聯物件的實現。
這是 FBRetainCycleDetector 系列文章中的第三篇,第四篇也是最後一篇文章會介紹 FBRetainCycleDetector 是如何獲取 block 持有的強引用的,這也是我覺得整個框架中實現最精彩的一部分。
關注倉庫,及時獲得更新:iOS-Source-Code-Analyze
Follow: Draveness · Github