1.使用 runtime 為 Category 動態(tài)關(guān)聯(lián)對象
在分類中可以用 @property 添加屬性钠右,但是不會自動生成私有成員變量嗤疯,也不會生成 set/get 方法的實現(xiàn)壹堰,只會生成 set/get 的聲明,所以要在分類中添加屬性,需要借助關(guān)聯(lián)對象运提。
使用關(guān)聯(lián)對象模擬屬性
相關(guān) API
objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy);
objc_getAssociatedObject(id object, const void *key);
說明:
- 參數(shù)一:id object : 給哪個對象添加屬性鳍寂,這里要給自己添加屬性改含,用 self。
- 參數(shù)二:void * == id key : 屬性名迄汛,根據(jù)key獲取關(guān)聯(lián)對象的屬性的值捍壤,在objc_getAssociatedObject中通過次key獲得屬性的值并返回骤视。key值只要是一個指針即可,我們可以傳入@selector(name)
- 參數(shù)三:id value : 關(guān)聯(lián)的值鹃觉,也就是set方法傳入的值給屬性去保存专酗。
- 參數(shù)四:objc_AssociationPolicy policy : 策略,屬性以什么形式保存盗扇。有以下幾種
typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) {
OBJC_ASSOCIATION_ASSIGN = 0, // 指定一個弱引用相關(guān)聯(lián)的對象
OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, // 指定相關(guān)對象的強引用祷肯,非原子性
OBJC_ASSOCIATION_COPY_NONATOMIC = 3, // 指定相關(guān)的對象被復(fù)制,非原子性
OBJC_ASSOCIATION_RETAIN = 01401, // 指定相關(guān)對象的強引用疗隶,原子性
OBJC_ASSOCIATION_COPY = 01403 // 指定相關(guān)的對象被復(fù)制佑笋,原子性
};
key 的常見定義方法
static void *MyKey = &MyKey;
objc_setAssociatedObject(obj, MyKey, value, OBJC_ASSOCIATION_RETAIN_NONATOMIC)
objc_getAssociatedObject(obj, MyKey)
static char MyKey;
objc_setAssociatedObject(obj, &MyKey, value, OBJC_ASSOCIATION_RETAIN_NONATOMIC)
objc_getAssociatedObject(obj, &MyKey)
使用屬性名作為key
objc_setAssociatedObject(obj, @"property", value, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
objc_getAssociatedObject(obj, @"property");
使用get方法的@selecor作為key
objc_setAssociatedObject(obj, @selector(getter), value, OBJC_ASSOCIATION_RETAIN_NONATOMIC)
objc_getAssociatedObject(obj, @selector(getter))
demo
-(void)setName:(NSString *)name
{
objc_setAssociatedObject(self, @"name",name, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
-(NSString *)name
{
return objc_getAssociatedObject(self, @"name");
}
- (void)removeAssociatedObjects
{
// 移除所有關(guān)聯(lián)對象
objc_removeAssociatedObjects(self);
// 移除單個關(guān)聯(lián)對象,置我 nil 即可
self.name = nil;
}
2.關(guān)聯(lián)對象實現(xiàn)原理
關(guān)聯(lián)對象相關(guān)類
- AssociationsManager
- AssociationsHashMap
- ObjectAssociationMap
- ObjcAssociation
相關(guān)結(jié)構(gòu)
原理圖示
3.源碼解讀
objc_setAssociatedObject 函數(shù)
objc_setAssociatedObject 函數(shù)
_object_set_associative_reference 函數(shù)
AssociationsManager
通過 AssociationsManager 內(nèi)部源碼發(fā)現(xiàn)抽减,AssociationsManager 內(nèi)部有一個AssociationsHashMap 對象允青。
AssociationsHashMap
通過 AssociationsHashMap 內(nèi)部源碼我們發(fā)現(xiàn) AssociationsHashMap 繼承自 unordered_map 首先來看一下 unordered_map 內(nèi)的源碼
從 unordered_map 源碼中我們可以看出 _Key 和 _Tp 也就是前兩個參數(shù)對應(yīng)著map中的Key和Value,那么對照上面 AssociationsHashMap 內(nèi)源碼發(fā)現(xiàn) _Key 中傳入的是disguised_ptr_t卵沉,_Tp 中傳入的值則為 ObjectAssociationMap*颠锉。
ObjectAssociationMap 中同樣以 key、Value 的方式存儲著 ObjcAssociation史汗。
ObjcAssociation 中
我們發(fā)現(xiàn)ObjcAssociation存儲著_policy琼掠、_value,而這兩個值我們可以發(fā)現(xiàn)正是我們調(diào)用objc_setAssociatedObject函數(shù)傳入的值停撞,也就是說我們在調(diào)用objc_setAssociatedObject函數(shù)中傳入的value和policy這兩個值最終是存儲在ObjcAssociation中的瓷蛙。
總結(jié)圖
關(guān)聯(lián)對象并不是存儲在被關(guān)聯(lián)對象本身內(nèi)存中,而是存儲在全局的統(tǒng)一的一個AssociationsManager 中戈毒,如果設(shè)置關(guān)聯(lián)對象為 nil艰猬,就相當(dāng)于是移除關(guān)聯(lián)對象。
面試題
- Category 能否添加成員變量埋市?如果可以冠桃,如何給 Category 添加成員變量?
答:默認(rèn)情況下道宅,因為分類底層結(jié)構(gòu)的限制食听,不能添加成員變量到分類中,但是可以通過 runtime 的方式間接實現(xiàn)添加成員變量的效果污茵。
分類底層結(jié)構(gòu)沒有 ivar_list 這個結(jié)構(gòu)樱报。
- 關(guān)聯(lián)對象的 policy 中為何沒有 weak
答:關(guān)聯(lián)對象是保存在一個全局的 Map 中,此 Map 為 HashMap泞当,內(nèi)部不能保存一個弱引用對象