1. 程式人生 > >iOS關聯物件技術底層原理

iOS關聯物件技術底層原理

iOS 通過 runtime 的 API 可以給分類新增屬性,關聯屬性總共有下邊3個 API

///獲取某個物件的關聯屬性
id objc_getAssociatedObject(id object, const void *key) {
    return _object_get_associative_reference(object, (void *)key);
}

///給某個物件新增關聯屬性
void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy) {
    _object_set_associative_reference(object
, (void *)key, value, policy); } ///移除物件所有的關聯屬性 void objc_removeAssociatedObjects(id object)

通過 runtime 的原始碼可以看出關聯屬性並沒有新增到 category_t(分類)裡邊,執行時也不會合併到元類物件裡邊,而是儲存在一個全域性的AssociationsManager 裡邊,下邊是這個 AssociationsManager 包含的層級關係.

image.png

所有的關聯屬性 和 獲取關聯屬性 移除關聯屬性都是通過一個 AssociationsManager來操作,類似於 OC 中 NSFileManager 的角色,通過傳遞進來的物件作為地址 取出這個物件所對應的關聯列表,然後通過key 取出這個關聯列表的關聯屬性 ObjcAssociation, ObjcAssociation 包含了關聯策略 和 關聯值.

下邊我會通過解讀原始碼 來分析AssociationsManager是如何要關聯的值和物件建立聯絡的.

AssociationsManager 是一個 C++的類 用來進行對關聯物件的屬性新增 和 查詢 移除等操作


class AssociationsManager {
    static spinlock_t _lock;
    static AssociationsHashMap *_map;               // associative references:  object pointer -> PtrPtrHashMap. 這個_ map 裡邊儲存的有關聯列表
public: AssociationsManager() { _lock.lock(); } ~AssociationsManager() { _lock.unlock(); } AssociationsHashMap &associations() { //可以看成是隻初始化一次 類似與單例 if (_map == NULL) _map = new AssociationsHashMap(); return *_map; } };

關聯列表是一個 hashMap 類似於 OC 的 NSDictionary ,其中用 disguised_ptr_t 作為 key , ObjectAssociationMap * 作為一個 value

disguised_ptr_t 是 uintptr_t 的型別

intptr_t 和uintptr_t 型別用來存放指標地址。它們提供了一種可移植且安全的方法宣告指標,而且和系統中使用的指標長度相同,對於把指標轉化成整數形式來說很有用。
可以把disguised_ptr_t理解為一個指標型別的變數


   class AssociationsHashMap : public unordered_map<disguised_ptr_t, ObjectAssociationMap *, DisguisedPointerHash, DisguisedPointerEqual, AssociationsHashMapAllocator> {
    public:
        void *operator new(size_t n) { return ::malloc(n); }
        void operator delete(void *ptr) { ::free(ptr); }
    };

````

ObjectAssociationMap 也是一個 HashMap 存放的是 一個 void * key 就是關聯屬性時傳進來的 key ,  ObjcAssociation 存放的關聯屬性策略和值的資訊




<div class="se-preview-section-delimiter"></div>
class ObjectAssociationMap : public std::map<void *, ObjcAssociation, ObjectPointerLess, ObjectAssociationMapAllocator> {
public:
    void *operator new(size_t n) { return ::malloc(n); }
    void operator delete(void *ptr) { ::free(ptr); }
};
ObjcAssociation 關聯屬性資訊類 存放了關聯策略 和 傳遞進來關聯的值 id 型別




<div class="se-preview-section-delimiter"></div>

class ObjcAssociation {
uintptr_t _policy;
id _value;
public:
ObjcAssociation(uintptr_t policy, id value) : _policy(policy), _value(value) {}
ObjcAssociation() : _policy(0), _value(nil) {}

    uintptr_t policy() const { return _policy; }
    id value() const { return _value; }

    bool hasValue() { return _value != nil; }
};

下邊是對 objc_getAssociatedObject , objc_setAssociatedObject , objc_removeAssociatedObjects 具體實現的分析

objc_setAssociatedObject 新增關聯屬性的 API





<div class="se-preview-section-delimiter"></div>

void _object_set_associative_reference(id object, void *key, id value, uintptr_t policy) {
// retain the new value (if any) outside the lock.
/// 舊的關聯物件 因為關聯屬性時如果傳 nil 可能會替換舊的關聯屬性 ,這就是移除某個關聯屬性時傳 nil 的原因
ObjcAssociation old_association(0, nil);
id new_value = value ? acquireValue(value, policy) : nil;
{
AssociationsManager manager;
///獲取關聯屬性列表 ,取出來的列表是以物件為單位的 ,即某個物件的關聯列表 ,這樣就可以單獨的關聯某個物件的關聯屬性 而不與其他物件隔離開
AssociationsHashMap &associations(manager.associations());
/// 將要新增關聯屬性的物件產生一個記憶體地址 做 key 儲存 它的關聯屬性
disguised_ptr_t disguised_object = DISGUISE(object);
/// 如果要關聯的值不為空 ,不為空時 就需要判斷這個屬性和 key 是不是第一天新增 ,即 void *key, id value 都是第一次傳遞進來
if (new_value) {
AssociationsHashMap::iterator i = associations.find(disguised_object);
/// 根據這個物件取出的這個物件關聯列表存在
if (i != associations.end()) {
///取出這個物件關聯所有的屬性列表
ObjectAssociationMap *refs = i->second;
///根據 可以 取出某個屬性的關聯字典 如果為空 就新增到關聯字典裡邊 ,不為空就對舊值就行替換操作
ObjectAssociationMap::iterator j = refs->find(key);
if (j != refs->end()) { ///取出來的字典不為空
old_association = j->second; //取出舊值 後邊對這個舊值進行 release 操作
///將新值存放到 key 對應的字典中去
j->second = ObjcAssociation(policy, new_value);
} else { ///沒有舊值直接將新值新增到字典裡
(*refs)[key] = ObjcAssociation(policy, new_value);
}
} else {
如果 key 物件的字典不存在 就建立一個字典 (hashMap 類似於字典的功能,本文為了方便理解將它稱為字典)
ObjectAssociationMap *refs = new ObjectAssociationMap;
associations[disguised_object] = refs;
///將要關聯屬性和策略封裝到一個ObjcAssociation類裡邊 並根據 key 新增到這個字典裡
(*refs)[key] = ObjcAssociation(policy, new_value);
object->setHasAssociatedObjects();
}
} else {
///如果新增關聯的屬性為空時 就需要取出之前關聯的值 並把它擦除掉 相當於removeObjectForKey
///還是根據物件記憶體地址找到它的關聯屬性列表 ,然後通過 key 找到它關聯屬性的實體(ObjcAssociation這個類) 最後擦除掉 相當於 free 從記憶體中移除
AssociationsHashMap::iterator i = associations.find(disguised_object);
if (i != associations.end()) {
ObjectAssociationMap *refs = i->second;
ObjectAssociationMap::iterator j = refs->find(key);
if (j != refs->end()) {
old_association = j->second;
refs->erase(j);
}
}
}
}
// release the old value (outside of the lock).
if (old_association.hasValue()) ReleaseValue()(old_association);
}


objc_getAssociatedObject 關聯物件取值的操作 





<div class="se-preview-section-delimiter"></div>

id _object_get_associative_reference(id object, void *key) {
id value = nil;
uintptr_t policy = OBJC_ASSOCIATION_ASSIGN;
{
///還是通過 AssociationsManager 找到所有關聯物件類別 ,然後通過傳入 object 找到某個物件的關聯列表 ,然後通過 key 找到這個物件關聯屬性列表的某個實體(ObjcAssociation) 最後根據關聯策略返回這個屬性的值
AssociationsManager manager;
AssociationsHashMap &associations(manager.associations());
disguised_ptr_t disguised_object = DISGUISE(object);
AssociationsHashMap::iterator i = associations.find(disguised_object);
if (i != associations.end()) { ///如果這個物件的關聯列表存在
ObjectAssociationMap *refs = i->second;
ObjectAssociationMap::iterator j = refs->find(key);
if (j != refs->end()) { ///如果物件關聯列表的屬性存在
ObjcAssociation &entry = j->second;
value = entry.value();
policy = entry.policy();
///取出關聯值和策略 傳送訊息 類似與 [obj retain]
if (policy & OBJC_ASSOCIATION_GETTER_RETAIN) ((id(*)(id, SEL))objc_msgSend)(value, SEL_retain);
}
}
}
/// 如果這個物件是延時釋放的型別 類似與 OC Array String 這些不是 alloc 來的物件 都要執行 [obj autorelease]來釋放
if (value && (policy & OBJC_ASSOCIATION_GETTER_AUTORELEASE)) {
((id(*)(id, SEL))objc_msgSend)(value, SEL_autorelease);
}
return value;
}


objc_removeAssociatedObjects 移除該物件所有的關聯屬性列表





<div class="se-preview-section-delimiter"></div>

void _object_remove_assocations(id object) {
vector< ObjcAssociation,ObjcAllocator > elements;
{
AssociationsManager manager;
AssociationsHashMap &associations(manager.associations());
if (associations.size() == 0) return;
disguised_ptr_t disguised_object = DISGUISE(object);
AssociationsHashMap::iterator i = associations.find(disguised_object);
///如果這個物件有關聯的屬性列表 那麼久便利它關聯的屬性列表 然後通過便利將這些關聯內容 一個個從字典裡邊擦除 先擦除物件列表關聯的屬性列表 然後將這個物件關聯屬性的 hashMap 擦除掉 相當於 [dict removeAllObjects] 然後再從全域性 AssociationsManager 移除 這個物件關聯的字典 , 又相當於 從一個全域性大字典裡 把 dict這個物件的小字典 給移除了
if (i != associations.end()) {
// copy all of the associations that need to be removed.
ObjectAssociationMap *refs = i->second;
for (ObjectAssociationMap::iterator j = refs->begin(), end = refs->end(); j != end; ++j) {
elements.push_back(j->second);
}
// remove the secondary table.
delete refs;
associations.erase(i);
}
}
// the calls to releaseValue() happen outside of the lock.
for_each(elements.begin(), elements.end(), ReleaseValue());
}

““

以上程式碼看起來並不難 除了一些 C++ 語法難以理解外 也並不需要完全知道每行程式碼怎麼實現 ,大概思路就是 通過全域性大字典 ,找到某個物件相關的小字典 ,然後這個小字典裡存放了 一個key 對應一個屬性值,最後取出這個管理屬性的策略和值

寫到最後 AssociationsManager 這個類不是一個單例類

class AssociationsManager {
static spinlock_t _lock;
static AssociationsHashMap *_map; // associative references: object pointer -> PtrPtrHashMap.
public:
AssociationsManager() { _lock.lock(); } //預設無參建構函式 物件建立時自動呼叫 執行加鎖 這樣多個執行緒訪問 _map 時不會出現問題
~AssociationsManager() { _lock.unlock(); } //解構函式 物件是否時候進行解鎖的操作

///可以看做單例物件 在 AssociationsManager 建立時候 加鎖 當 AssociationsManager 釋放時候 解鎖 ,防止多執行緒訪問時候 對同一個 _map 多次建立 是一種懶漢模式單例
AssociationsHashMap &associations() {
if (_map == NULL)
_map = new AssociationsHashMap();
return *_map;
}
};

它裡邊有個 spinlock_t鎖 對 _map 這個全域性唯一的例項 進行加鎖和解鎖 ,由於懶漢模式的單例 需要在多個執行緒訪問 _map 時候進行加鎖保護

“`

void _object_remove_assocations(id object) {
vector< ObjcAssociation,ObjcAllocator > elements;
{
AssociationsManager manager;
AssociationsHashMap &associations(manager.associations());
if (associations.size() == 0) return;
disguised_ptr_t disguised_object = DISGUISE(object);
AssociationsHashMap::iterator i = associations.find(disguised_object);
///如果這個物件有關聯的屬性列表 那麼久便利它關聯的屬性列表 然後通過便利將這些關聯內容 一個個從字典裡邊擦除 先擦除物件列表關聯的屬性列表 然後將這個物件關聯屬性的 hashMap 擦除掉 相當於 [dict removeAllObjects] 然後再從全域性 AssociationsManager 移除 這個物件關聯的字典 , 又相當於 從一個全域性大字典裡 把 dict這個物件的小字典 給移除了
if (i != associations.end()) {
// copy all of the associations that need to be removed.
ObjectAssociationMap *refs = i->second;
for (ObjectAssociationMap::iterator j = refs->begin(), end = refs->end(); j != end; ++j) {
elements.push_back(j->second);
}
// remove the secondary table.
delete refs;
associations.erase(i);
}
}
// the calls to releaseValue() happen outside of the lock.
for_each(elements.begin(), elements.end(), ReleaseValue());
}

““

以上程式碼看起來並不難 除了一些 C++ 語法難以理解外 也並不需要完全知道每行程式碼怎麼實現 ,大概思路就是 通過全域性大字典 ,找到某個物件相關的小字典 ,然後這個小字典裡存放了 一個key 對應一個屬性值,最後取出這個管理屬性的策略和值

寫到最後 AssociationsManager 這個類不是一個單例類

class AssociationsManager {
static spinlock_t _lock;
static AssociationsHashMap *_map; // associative references: object pointer -> PtrPtrHashMap.
public:
AssociationsManager() { _lock.lock(); } //預設無參建構函式 物件建立時自動呼叫 執行加鎖 這樣多個執行緒訪問 _map 時不會出現問題
~AssociationsManager() { _lock.unlock(); } //解構函式 物件是否時候進行解鎖的操作

///可以看做單例物件 在 AssociationsManager 建立時候 加鎖 當 AssociationsManager 釋放時候 解鎖 ,防止多執行緒訪問時候 對同一個 _map 多次建立 是一種懶漢模式單例
AssociationsHashMap &associations() {
if (_map == NULL)
_map = new AssociationsHashMap();
return *_map;
}
};

它裡邊有個 spinlock_t鎖 對 _map 這個全域性唯一的例項 進行加鎖和解鎖 ,由於懶漢模式的單例 需要在多個執行緒訪問 _map 時候進行加鎖保護

好了,我是大兵布萊恩特,歡迎加入博主技術交流群,iOS 開發交流群

QQ20180712-0.png