前言
使用 Category 為已經(jīng)存在的類添加方法是我們很熟悉的常規(guī)操作,但是如果在 Category 中為類添加屬性 @property ,則編譯器會(huì)立即給我們?nèi)缦戮?
Property 'categoryProperty' requires method 'categoryProperty' to be defined - use @dynamic or provide a method implementation in this category.
Property 'categoryProperty' requires method 'setCategoryProperty:' to be defined - use @dynamic or provide a method implementation in this category
提示我們需要手動(dòng)為屬性添加 setter gettr 方法或者使用 @dynamic 在運(yùn)行時(shí)實(shí)現(xiàn)這些方法。 即明確的告訴我們在分類中 @property 并不會(huì)自動(dòng)生成實(shí)例變量以及存取方法。
不是說好的使用 @property ,編譯器會(huì)自動(dòng)幫我們生成實(shí)例變量和對應(yīng)的 setter 和 getter 方法嗎,此機(jī)制只能在類定義中實(shí)現(xiàn),因?yàn)樵诜诸愔?,類的?shí)例變量的布局已經(jīng)固定,使用 @property 已經(jīng)無法向固定的布局中添加新的實(shí)例變量,所以我們需要使用關(guān)聯(lián)對象以及兩個(gè)方法來模擬構(gòu)成屬性的三個(gè)要素。
示例代碼:
#import "HMObject.h"
NS_ASSUME_NONNULL_BEGIN
@interface HMObject (category)
// 在分類中添加一個(gè)屬性
@property (nonatomic, copy) NSString *categoryProperty;
@end
NS_ASSUME_NONNULL_END
#import "HMObject+category.h"
#import <objc/runtime.h>
@implementation HMObject (category)
- (NSString *)categoryProperty {
// _cmd 代指當(dāng)前方法的選擇子,即 @selector(categoryProperty)
return objc_getAssociatedObject(self, _cmd);
}
- (void)setCategoryProperty:(NSString *)categoryProperty {
objc_setAssociatedObject(self,
@selector(categoryProperty),
categoryProperty,
OBJC_ASSOCIATION_COPY_NONATOMIC);
}
@end
此時(shí)我們可以使用關(guān)聯(lián)對象 Associated Object 來手動(dòng)為 categoryProperty 添加存取方法,接下來我們對示例代碼一步一步進(jìn)行分析。
在類定義中使用 @property
在類定義中我們使用 @property 為類添加屬性,如果不使用 @dynamic 標(biāo)識(shí)該屬性的話,編譯器會(huì)自動(dòng)幫我們生成一個(gè)名字為下劃線加屬性名的實(shí)例變量和該屬性的 setter 和 getter 方法。我們編寫如下代碼:
// .h 中如下書寫
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface HMObject : NSObject
@property (nonatomic, copy) NSString *cusProperty;
@end
NS_ASSUME_NONNULL_END
// .m 中什么都不做
#import "HMObject.h"
@implementation HMObject
// @dynamic cusProperty;
@end
編譯器會(huì)自動(dòng)幫我們做如下三件事:
- 添加實(shí)例變量
_cusProperty - 添加
setter方法setCusProperty - 添加
getter方法cusProperty
即如下 HMObject.m 代碼實(shí)現(xiàn):
#import "HMObject.h"
@implementation HMObject
//@dynamic cusProperty;
{
NSString *_cusProperty;
}
- (void)setCusProperty:(NSString *)cusProperty {
_cusProperty = cusProperty;
}
- (NSString *)cusProperty {
return _cusProperty;
}
@end
驗(yàn)證 @property
下面我們通過 LLDB 進(jìn)行驗(yàn)證,首先我們把 HMObject.m 的代碼都注釋掉,只留下 HMObject.h 中的 cusProperty 屬性。 然后在 main 函數(shù)中編寫如下代碼:
Class cls = NSClassFromString(@"HMObject");
NSLog(@"%@", cls); // :arrow_left: 這里打一個(gè)斷點(diǎn)
開始驗(yàn)證:
這里我們也可以使用 runtime 的 class_copyPropertyList 、 class_copyMethodList 、 class_copyIvarList 三個(gè)函數(shù)來分別獲取 HMObject 的屬性列表、方法列表和成員變量列表來驗(yàn)證編譯器為我們自動(dòng)生成了什么內(nèi)容,但是這里我們采用一種更為簡單的方法,僅通過控制臺(tái)打印即可驗(yàn)證。
- 找到
cls的bits:
(lldb) x/5gx cls
0x1000022e8: 0x00000001000022c0 (isa) 0x00000001003ee140 (superclass)
0x1000022f8: 0x00000001003e84a0 0x0000001c00000000 (cache_t)
0x100002308: 0x0000000101850640 (bits)
- 強(qiáng)制轉(zhuǎn)換
class_data_bits_t指針
(lldb) p (class_data_bits_t *)0x100002308
(class_data_bits_t *) $1 = 0x0000000100002308
- 取得
class_rw_t *
(lldb) p $1->data()
(class_rw_t *) $2 = 0x0000000101850640
- 取得
class_ro_t *
(lldb) p $2->ro
(const class_ro_t *) $3 = 0x0000000100002128
- 打印
ro內(nèi)容
(lldb) p *$3
(const class_ro_t) $4 = {
flags = 388
instanceStart = 8
instanceSize = 16
reserved = 0
ivarLayout = 0x0000000100000ee6 "\x01"
name = 0x0000000100000edd "HMObject" // 類名
baseMethodList = 0x0000000100002170 // 方法列表
baseProtocols = 0x0000000000000000 // 遵循協(xié)議為空
ivars = 0x00000001000021c0 // 成員變量
weakIvarLayout = 0x0000000000000000
baseProperties = 0x00000001000021e8 // 屬性
_swiftMetadataInitializer_NEVER_USE = {}
}
- 打印
ivars
(lldb) p $4.ivars
(const ivar_list_t *const) $5 = 0x00000001000021c0
(lldb) p *$5
(const ivar_list_t) $6 = {
entsize_list_tt<ivar_t, ivar_list_t, 0> = {
entsizeAndFlags = 32
count = 1 // 有 1 個(gè)成員變量
first = {
offset = 0x00000001000022b8
// 看到名字為 _cusProperty 的成員變量
name = 0x0000000100000ef6 "_cusProperty"
type = 0x0000000100000f65 "@\"NSString\""
alignment_raw = 3
size = 8
}
}
}
- 打印
baseProperties
(lldb) p $4.baseProperties
(property_list_t *const) $7 = 0x00000001000021e8
(lldb) p *$7
(property_list_t) $8 = {
entsize_list_tt<property_t, property_list_t, 0> = {
entsizeAndFlags = 16
count = 1
first = (name = "cusProperty", attributes = "T@\"NSString\",C,N,V_cusProperty")
}
}
看到只有一個(gè)名字是 cusProperty 的屬性,屬性的 attributes 是: "T@\"NSString\",C,N,V_cusProperty"
|code|meaning| |...|...| |T|類型| |C|copy| |N|nonatomic| |V|實(shí)例變量|
- 打印
baseMethodList
(lldb) p $4.baseMethodList
(method_list_t *const) $9 = 0x0000000100002170
(lldb) p *$9
(method_list_t) $10 = {
entsize_list_tt<method_t, method_list_t, 3> = {
entsizeAndFlags = 26
count = 3 // 有 3 個(gè) method
first = {
// 第一個(gè)正是 cusProperty 的 getter 函數(shù)
name = "cusProperty"
types = 0x0000000100000f79 "@16@0:8"
imp = 0x0000000100000c30 (KCObjcTest`-[HMObject cusProperty])
}
}
}
看到方法的 TypeEncoding 如下: types = 0x0000000100000f79 "@16@0:8" 從左向右分別表示的含義是: @ 表示返回類型是 OC 對象,16 表示所有參數(shù)總長度,再往后 @ 表示第一個(gè)參數(shù)的類型,對應(yīng)函數(shù)調(diào)用的 self 類型,0 表示從第 0 位開始,分隔號(hào) : 表示第二個(gè)參數(shù)類型,對應(yīng) SEL ,8 表示從第 8 位開始,因?yàn)榍懊娴囊粋€(gè)參數(shù) self 占 8 個(gè)字節(jié)。下面開始是自定義參數(shù),因?yàn)?getter 函數(shù)沒有自定義函數(shù),所以只有 self 和 SEL 參數(shù)就結(jié)束了。 對應(yīng)的函數(shù)原型正是 objc_msgSend 函數(shù):
void
objc_msgSend(void /* id self, SEL op, ... */ )
- 打印剩下的兩個(gè)
method
(lldb) p $10.get(1)
(method_t) $11 = {
name = "setCusProperty:"
types = 0x0000000100000f81 "v24@0:8@16"
imp = 0x0000000100000c60 (KCObjcTest`-[HMObject setCusProperty:])
}
(lldb) p $10.get(2)
(method_t) $12 = {
name = ".cxx_destruct"
types = 0x0000000100000f71 "v16@0:8"
imp = 0x0000000100000c00 (KCObjcTest`-[HMObject .cxx_destruct])
}
看到一個(gè)是 cusProperty 的 setter 函數(shù),一個(gè)是 C++ 的析構(gòu)函數(shù)。
為了做出對比,我們注釋掉 HMObject.h 中的 cusProperty 屬性,然后重走上面的流程,可打印出如下信息:
(lldb) x/5gx cls
0x100002240: 0x0000000100002218 0x00000001003ee140
0x100002250: 0x00000001003e84a0 0x0000001000000000
0x100002260: 0x00000001006696c0
(lldb) p (class_data_bits_t *)0x100002260
(class_data_bits_t *) $1 = 0x0000000100002260
(lldb) p $1->data()
(class_rw_t *) $2 = 0x00000001006696c0
(lldb) p $2->ro
(const class_ro_t *) $3 = 0x0000000100002118
(lldb) p *$3
(const class_ro_t) $4 = {
flags = 128
instanceStart = 8
instanceSize = 8
reserved = 0
ivarLayout = 0x0000000000000000
name = 0x0000000100000f22 "HMObject"
baseMethodList = 0x0000000000000000
baseProtocols = 0x0000000000000000
ivars = 0x0000000000000000
weakIvarLayout = 0x0000000000000000
baseProperties = 0x0000000000000000
_swiftMetadataInitializer_NEVER_USE = {}
}
(lldb)
可看到 ivars 、 baseProperties 和 baseMethodList 都是 0x0000000000000000 ,即編譯器沒有為 HMObject 生成屬性、成員變量和函數(shù)。 至此 @property 的作用可得到完整證明。
作為一個(gè)開發(fā)者,有一個(gè)學(xué)習(xí)的氛圍跟一個(gè)交流圈子特別重要,這有個(gè)iOS交流群:642363427,不管你是小白還是大牛歡迎入駐 ,分享BAT,阿里面試題、面試經(jīng)驗(yàn),討論技術(shù),iOS開發(fā)者一起交流學(xué)習(xí)成長!
@property 能夠?yàn)槲覀冏詣?dòng)生成實(shí)例變量以及存取方法,而這三者構(gòu)成了屬性這個(gè)類似于語法糖的概念,為我們提供了更便利的點(diǎn)語法來訪問屬性:
self.property 等價(jià)于 [self property]; self.property = value; 等價(jià)于 [self setProperty:value];
習(xí)慣于 C/C++ 結(jié)構(gòu)體和結(jié)構(gòu)體指針取結(jié)構(gòu)體成員變量時(shí)使用 . 和 -> 。初見 OC 的點(diǎn)語法時(shí)有一絲疑問, self 明明是一個(gè)指針,訪問它的成員變量時(shí)為什么用 . 呢?如果按 C/C++ 的規(guī)則,不是應(yīng)該使用 self->_property 嗎?
這里我們應(yīng)與 C/C++ 的點(diǎn)語法做出區(qū)別理解, OC 中點(diǎn)語法是用來幫助我們便捷訪問屬性的,在類內(nèi)部我們可以使用 _proerty 、 self->_propery 和 self.property 三種方式訪問同一個(gè)成員變量,區(qū)別在于使用 self.property 時(shí)是通過調(diào)用 property 的 setter 和 getter 來讀取成員變量,而前兩種則是直接讀取,因此當(dāng)我們重寫屬性的 setter 和 getter 并在內(nèi)部做一些自定義操作時(shí),我們一定要記得使用 self.property 來訪問屬性。
Associated Object
我們使用 objc_setAssociatedObject 和 objc_getAssociatedObject 來分別模擬屬性的存取方法,而使用關(guān)聯(lián)對象模擬實(shí)例變量。 runtime.h 中定義了如下三個(gè)與關(guān)聯(lián)對象相關(guān)的函數(shù)接口:
/**
* Sets an associated value for a given object using a given key and association policy.
* 使用給定的鍵和關(guān)聯(lián)策略為給定的對象設(shè)置關(guān)聯(lián)的值。
*
* @param object The source object for the association.
* 關(guān)聯(lián)的源對象
*
* @param key The key for the association.
* 關(guān)聯(lián)的 key
* @param value The value to associate with the key key for object.
* Pass nil to clear an existing association.
* 與對象的鍵相關(guān)聯(lián)的值。傳遞 nil 以清除現(xiàn)有的關(guān)聯(lián)。
*
* @param policy The policy for the association. For possible values, see “Associative Object Behaviors.”
* 關(guān)聯(lián)策略
*
* @see objc_setAssociatedObject
* @see objc_removeAssociatedObjects
*/
OBJC_EXPORT void
objc_setAssociatedObject(id _Nonnull object, const void * _Nonnull key,
id _Nullable value, objc_AssociationPolicy policy)
OBJC_AVAILABLE(10.6, 3.1, 9.0, 1.0, 2.0);
/**
* Returns the value associated with a given object for a given key.
* 返回與給定鍵的給定對象關(guān)聯(lián)的值
*
* @param object The source object for the association.
* 關(guān)聯(lián)的源對象
* @param key The key for the association.
* 關(guān)聯(lián)的 key
*
* @return The value associated with the key \e key for \e object.
*
* @see objc_setAssociatedObject
*/
OBJC_EXPORT id _Nullable
objc_getAssociatedObject(id _Nonnull object, const void * _Nonnull key)
OBJC_AVAILABLE(10.6, 3.1, 9.0, 1.0, 2.0);
/**
* Removes all associations for a given object.
* 刪除給定對象的所有關(guān)聯(lián)
*
* @param object An object that maintains associated objects.
*
* @note The main purpose of this function is to make it easy to return an object
* to a "pristine state”. You should not use this function for general removal of
* associations from objects, since it also removes associations that other clients
* may have added to the object. Typically you should use \c objc_setAssociatedObject
* with a nil value to clear an association.
*
* 意指此函數(shù)會(huì)一下刪除對象全部的關(guān)聯(lián)對象,如果我們想要?jiǎng)h除指定的關(guān)聯(lián)對象,
* 應(yīng)該使用 objc_setAssociatedObject 函數(shù)把 value 參數(shù)傳遞 nil 即可。
*
* 此功能的主要目的是使對象輕松返回“原始狀態(tài)”,因此不應(yīng)從該對象中普遍刪除關(guān)聯(lián),
* 因?yàn)樗€會(huì)刪除其他 clients 可能已添加到該對象的關(guān)聯(lián)。
* 通常,您應(yīng)該將 objc_setAssociatedObject 與 nil 一起使用以清除指定關(guān)聯(lián)。
*
* @see objc_setAssociatedObject
* @see objc_getAssociatedObject
*/
OBJC_EXPORT void
objc_removeAssociatedObjects(id _Nonnull object)
OBJC_AVAILABLE(10.6, 3.1, 9.0, 1.0, 2.0);
const void *key
存取函數(shù)中的參數(shù) key 我們都使用了 @selector(categoryProperty) ,其實(shí)也可以使用靜態(tài)指針 static void * 類型的參數(shù)來代替,不過這里強(qiáng)烈建議使用 @selector(categoryProperty) 作為 key 傳入,因?yàn)檫@種方法省略了聲明參數(shù)的代碼,并且能很好地保證 key 的唯一性。
objc_AssociationPolicy policy
policy 代表關(guān)聯(lián)策略:
/**
* Policies related to associative references.
* These are options to objc_setAssociatedObject()
*/
typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) {
/**< Specifies a weak reference to the associated object. */
OBJC_ASSOCIATION_ASSIGN = 0,
/**< Specifies a strong reference to the associated object.
* The association is not made atomically. */
OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1,
/**< Specifies that the associated object is copied.
* The association is not made atomically. */
OBJC_ASSOCIATION_COPY_NONATOMIC = 3,
/**< Specifies a strong reference to the associated object.
* The association is made atomically. */
OBJC_ASSOCIATION_RETAIN = 01401,
/**< Specifies that the associated object is copied.
* The association is made atomically. */
OBJC_ASSOCIATION_COPY = 01403
};
注釋已經(jīng)解釋的很清楚了,即不同的策略對應(yīng)不同的修飾符: | objc_AssociationPolicy | 修飾符 | | ... | ... | | OBJC_ASSOCIATION_ASSIGN | assign | | OBJC_ASSOCIATION_RETAIN_NONATOMIC | nonatomic、strong | | OBJC_ASSOCIATION_COPY_NONATOMIC | nonatomic、copy | | OBJC_ASSOCIATION_RETAIN | atomic, strong | | OBJC_ASSOCIATION_COPY | atomic, copy |
objc-references.mm 文件包含了所有的核心操作,首先來分析相關(guān)的數(shù)據(jù)結(jié)構(gòu)。
ObjcAssociation
associated object 機(jī)制中用于保存 關(guān)聯(lián)策略 和 關(guān)聯(lián)值 。
class ObjcAssociation {
// typedef unsigned long uintptr_t;
uintptr_t _policy; // 關(guān)聯(lián)策略
id _value; // 關(guān)聯(lián)值
public:
// 構(gòu)造函數(shù),初始化列表初始化 policy 和 value
ObjcAssociation(uintptr_t policy, id value) : _policy(policy), _value(value) {}
// 構(gòu)造函數(shù),初始化列表,policy 初始化為 0, value 初始化為 nil
ObjcAssociation() : _policy(0), _value(nil) {}
// 復(fù)制構(gòu)造函數(shù)采用默認(rèn)
ObjcAssociation(const ObjcAssociation &other) = default;
// 賦值操作符采用默認(rèn)
ObjcAssociation &operator=(const ObjcAssociation &other) = default;
// 交換 policy 和 value
ObjcAssociation(ObjcAssociation &&other) : ObjcAssociation() {
swap(other);
}
inline void swap(ObjcAssociation &other) {
std::swap(_policy, other._policy);
std::swap(_value, other._value);
}
// 內(nèi)聯(lián)函數(shù)獲取 _policy
inline uintptr_t policy() const { return _policy; }
// 內(nèi)聯(lián)函數(shù)獲取 _value
inline id value() const { return _value; }
// 在 SETTER 時(shí)使用:判斷是否需要持有 value
inline void acquireValue() {
if (_value) {
switch (_policy & 0xFF) {
case OBJC_ASSOCIATION_SETTER_RETAIN:
// retain
_value = objc_retain(_value);
break;
case OBJC_ASSOCIATION_SETTER_COPY:
// copy
_value = ((id(*)(id, SEL))objc_msgSend)(_value, @selector(copy));
break;
}
}
}
// 在 SETTER 時(shí)使用:與上面的 acquireValue 函數(shù)對應(yīng),釋放舊值 value
inline void releaseHeldValue() {
if (_value && (_policy & OBJC_ASSOCIATION_SETTER_RETAIN)) {
// release 減少引用計(jì)數(shù)
objc_release(_value);
}
}
// 在 GETTER 時(shí)使用:根據(jù)關(guān)聯(lián)策略判斷是否對關(guān)聯(lián)值進(jìn)行 retain 操作
inline void retainReturnedValue() {
if (_value && (_policy & OBJC_ASSOCIATION_GETTER_RETAIN)) {
objc_retain(_value);
}
}
// 在 GETTER 時(shí)使用:判斷是否需要放進(jìn)自動(dòng)釋放池
inline id autoreleaseReturnedValue() {
if (slowpath(_value && (_policy & OBJC_ASSOCIATION_GETTER_AUTORELEASE))) {
return objc_autorelease(_value);
}
return _value;
}
};
ObjectAssociationMap
typedef DenseMap<const void *, ObjcAssociation> ObjectAssociationMap;
DenseMap 這里不在展開,把 ObjectAssociationMap 理解為一個(gè) key 是 const void * value 是 ObjcAssociation 的哈希表即可。
AssociationsHashMap
typedef DenseMap<DisguisedPtr<objc_object>, ObjectAssociationMap> AssociationsHashMap;
同上,把 AssociationsHashMap 理解為一個(gè) key 是 DisguisedPtr<objc_object> value 是 ObjectAssociationMap 的哈希表即可。 DisguisedPtr<objc_object> 可理解為把 objc_object 地址偽裝為一個(gè)整數(shù)。
AssociationsManager
AssociationsManager 的類定義不復(fù)雜,從數(shù)據(jù)結(jié)構(gòu)角度來看的話它是作為一個(gè) key 是 DisguisedPtr<objc_object> value 是 ObjectAssociationMap 的哈希表來用的,這么看它好像和上面的 AssociationsHashMap 有些重合,其實(shí)它內(nèi)部正是存儲(chǔ)了一個(gè)局部靜態(tài)的 AssociationsHashMap 用來存儲(chǔ)程序中所有的關(guān)聯(lián)對象。
AssociationsManagerLock
spinlock_t AssociationsManagerLock;
一個(gè)全局的自旋鎖(互斥鎖),保證 AssociationsManager 中對 AssociationsHashMap 操作的線程安全。
// class AssociationsManager manages a lock / hash table singleton pair.
// Allocating an instance acquires the lock
class AssociationsManager {
// Storage 模版類名
using Storage = ExplicitInitDenseMap<DisguisedPtr<objc_object>, ObjectAssociationMap>;
// 靜態(tài)變量 _mapStoreage,用于存儲(chǔ) AssociationsHashMap 數(shù)據(jù)
static Storage _mapStorage;
public:
// 構(gòu)造函數(shù) 獲取全局的 AssociationsManagerLock 加鎖
AssociationsManager() { AssociationsManagerLock.lock(); }
// 析構(gòu)函數(shù) AssociationsManagerLock 解鎖
~AssociationsManager() { AssociationsManagerLock.unlock(); }
// 返回內(nèi)部的保存的 AssociationsHashMap,
AssociationsHashMap &get() {
return _mapStorage.get();
}
// init 初始化函數(shù)實(shí)現(xiàn)只是 調(diào)用 Storage 的 init 函數(shù)
static void init() {
_mapStorage.init();
}
};
// 其實(shí)這里有點(diǎn)想不明白,明明 AssociationsManager 已經(jīng)定義了公開函數(shù) get 獲取內(nèi)部 _mapStorage 的數(shù)據(jù),
// 為什么這里在類定義外面還寫了這句代碼 ?
AssociationsManager::Storage AssociationsManager::_mapStorage;
管理 AssociationsHashMap 靜態(tài)變量。
總結(jié):
- 通過
AssociationsManager的get函數(shù)取得一個(gè)全局唯一AssociationsHashMap。 - 根據(jù)我們的原始對象的
DisguisedPtr<objc_object>從AssociationsHashMap取得ObjectAssociationMap。 - 根據(jù)我們指定的關(guān)聯(lián)
key(const void *key) 從ObjectAssociationMap取得ObjcAssociation。 -
ObjcAssociation的兩個(gè)成員變量,保存我們的關(guān)聯(lián)策略_policy和關(guān)聯(lián)值_value。
示例圖:

objc_setAssociatedObject
void
objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy) {
SetAssocHook.get()(object, key, value, policy);
}
SetAssocHook :
static ChainedHookFunction<objc_hook_setAssociatedObject> SetAssocHook{_base_objc_setAssociatedObject};
_base_objc_setAssociatedObject
static void
_base_objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
{
_object_set_associative_reference(object, key, value, policy);
}
forbidsAssociatedObjects
// class does not allow associated objects on its instances
#define RW_FORBIDS_ASSOCIATED_OBJECTS (1<<20)
bool forbidsAssociatedObjects() {
return (data()->flags & RW_FORBIDS_ASSOCIATED_OBJECTS);
}
try_emplace
// Inserts key,value pair into the map if the key isn't already in the map.
// 如果 key value 鍵值對在 map 中不存在則把它們插入 map
// The value is constructed in-place if the key is not in the map,
// otherwise it is not moved.
template <typename... Ts>
std::pair<iterator, bool> try_emplace(const KeyT &Key, Ts &&... Args) {
BucketT *TheBucket;
// 已存在
if (LookupBucketFor(Key, TheBucket))
return std::make_pair(
makeIterator(TheBucket, getBucketsEnd(), true),
false); // Already in map.
// Otherwise, insert the new element.
// 不存在,則插入新元素
TheBucket = InsertIntoBucket(TheBucket, Key, std::forward<Ts>(Args)...);
return std::make_pair(
makeIterator(TheBucket, getBucketsEnd(), true),
true);
}
setHasAssociatedObjects 設(shè)置對象的 uintptr_t has_assoc : 1; 位,標(biāo)記該對象有關(guān)聯(lián)對象,該對象 dealloc 時(shí)要進(jìn)行清理工作。
inline void
objc_object::setHasAssociatedObjects()
{
if (isTaggedPointer()) return;
retry:
isa_t oldisa = LoadExclusive(&isa.bits);
isa_t newisa = oldisa;
if (!newisa.nonpointer || newisa.has_assoc) {
ClearExclusive(&isa.bits);
return;
}
newisa.has_assoc = true;
if (!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)) goto retry;
}
_object_set_associative_reference
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; // 判空對象和關(guān)聯(lián)值都為 nil 則 return
// 判斷該類是否允許關(guān)聯(lián)對象
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 指針為 disguised
DisguisedPtr<objc_object> disguised{(objc_object *)object};
// 根據(jù)入?yún)?chuàng)建一個(gè) association
ObjcAssociation association{policy, value};
// retain the new value (if any) outside the lock.
// 在加鎖之前根據(jù)關(guān)聯(lián)策略判斷是否 retain/copy 入?yún)?value
association.acquireValue();
{
// 創(chuàng)建 mananger 臨時(shí)變量
// 這里還有一步連帶操作
// 在其構(gòu)造函數(shù)中 AssociationsManagerLock.lock() 加鎖
AssociationsManager manager;
// 取得全局的 AssociationsHashMap
AssociationsHashMap &associations(manager.get());
if (value) {
// 這里 DenseMap 對我們而言是一個(gè)黑盒,這里只要看 try_emplace 函數(shù)
// 在全局 AssociationsHashMap 中嘗試插入 <DisguisedPtr<objc_object>, ObjectAssociationMap>
// 返回值類型是 std::pair<iterator, bool>
auto refs_result = associations.try_emplace(disguised, ObjectAssociationMap{});
// 如果新插入成功
if (refs_result.second) {
/* it's the first association we make */
// 第一次建立 association
// 設(shè)置 uintptr_t has_assoc : 1; 位,標(biāo)記該對象存在關(guān)聯(lián)對象
object->setHasAssociatedObjects();
}
/* establish or replace the association */
// 重建或者替換 association
auto &refs = refs_result.first->second;
// 這里存在一個(gè)疑問,如果值對象第一關(guān)聯(lián)新值,且是 strong 強(qiáng)引用對象,
// 如果 association 里面一直存放的就是新值新策略,那執(zhí)行到函數(shù)結(jié)尾豈不是要執(zhí)行 release 操作了 ?
auto result = refs.try_emplace(key, std::move(association));
if (!result.second) {
// 替換
// 如果之前有舊值的話把舊值的成員變量交換到 association
// 然后在 函數(shù)執(zhí)行結(jié)束時(shí)把舊值根據(jù)對應(yīng)的策略判斷執(zhí)行 release
association.swap(result.first->second);
}
} else {
// value 為 nil 的情況,表示要把之前的關(guān)聯(lián)對象置為 nil
// 也可理解為移除指定的關(guān)聯(lián)對象
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);
// 清除指定的關(guān)聯(lián)對象
refs.erase(it);
// 如果當(dāng)前 object 的關(guān)聯(lián)對象為空了,則同時(shí)從全局的 AssociationsHashMap
// 中移除該對象
if (refs.size() == 0) {
associations.erase(refs_it);
}
}
}
}
// 析構(gòu) mananger 臨時(shí)變量
// 這里還有一步連帶操作
// 在其析構(gòu)函數(shù)中 AssociationsManagerLock.unlock() 解鎖
}
// release the old value (outside of the lock).
// 開始時(shí) retain 的是新入?yún)⒌?value, 這里釋放的是舊值,association 內(nèi)部的 value 已經(jīng)被替換了
association.releaseHeldValue();
}
函數(shù)執(zhí)行過程中有兩種情況:
value != nil
value == nil
函數(shù)流程圖:

如果看通了上面的 _object_set_associative_reference 則看 _object_get_associative_reference 是很容易看懂的。
objc_getAssociatedObject
id
objc_getAssociatedObject(id object, const void *key) {
return _object_get_associative_reference(object, key);
}
_object_get_associative_reference
id
_object_get_associative_reference(id object, const void *key)
{
// 局部變量
ObjcAssociation association{};
{
// 加鎖
AssociationsManager manager;
// 取得全局唯一的 AssociationsHashMap
AssociationsHashMap &associations(manager.get());
// 從全局的 AssociationsHashMap 中取得對象對應(yīng)的 ObjectAssociationMap
AssociationsHashMap::iterator i = associations.find((objc_object *)object);
if (i != associations.end()) {
// 如果存在
ObjectAssociationMap &refs = i->second;
// 從 ObjectAssocationMap 中取得 key 對應(yīng)的 ObjcAssociation
ObjectAssociationMap::iterator j = refs.find(key);
if (j != refs.end()) {
// 如果存在
association = j->second;
// 根據(jù)關(guān)聯(lián)策略判斷是否需要對 _value 執(zhí)行 retain 操作
association.retainReturnedValue();
}
}
// 解鎖
}
// 返回 _value 并根據(jù)關(guān)聯(lián)策略判斷是否需要放入自動(dòng)釋放池
return association.autoreleaseReturnedValue();
}
objc_removeAssociatedObjects
hasAssociatedObjects
inline bool
objc_object::hasAssociatedObjects()
{
if (isTaggedPointer()) return true;
if (isa.nonpointer) return isa.has_assoc;
return true;
}
objc_removeAssociatedObjects
void objc_removeAssociatedObjects(id object)
{
// 對象不為空,且 has_assoc 標(biāo)記為 true,表示該對象有關(guān)聯(lián)對象
if (object && object->hasAssociatedObjects()) {
_object_remove_assocations(object);
}
}
_object_remove_assocations
// Unlike setting/getting an associated reference,
// this function is performance sensitive because
// of raw isa objects (such as OS Objects) that can't
// track whether they have associated objects.
// 與 setting/getting 關(guān)聯(lián)引用不同,此函數(shù)對性能敏感,
// 因?yàn)樵嫉?isa 對象(例如 OS 對象)無法跟蹤它們是否具有關(guān)聯(lián)的對象。
void
_object_remove_assocations(id object)
{
// 對象對應(yīng)的 ObjectAssociationMap
ObjectAssociationMap refs{};
{
// 加鎖
AssociationsManager manager;
// 取得全局的 AssociationsHashMap
AssociationsHashMap &associations(manager.get());
// 取得對象的對應(yīng) ObjectAssociationMap,里面包含所有的 (key, ObjcAssociation)
AssociationsHashMap::iterator i = associations.find((objc_object *)object);
if (i != associations.end()) {
// 把 i->second 的內(nèi)容都轉(zhuǎn)入 refs 對象中
refs.swap(i->second);
// 從全局 AssociationsHashMap 移除對象的 ObjectAssociationMap
associations.erase(i);
}
// 解鎖
}
// release everything (outside of the lock).
// 遍歷對象的 ObjectAssociationMap 中的 (key, ObjcAssociation)
// 對 ObjcAssociation 的 _value 根據(jù) _policy 進(jìn)行釋放
for (auto &i: refs) {
i.second.releaseHeldValue();
}
}
關(guān)聯(lián)對象的本質(zhì)
在分類中到底能否實(shí)現(xiàn)屬性?首先要知道屬性是什么,屬性的概念決定了這個(gè)問題的答案。
- 如果把屬性理解為通過方法訪問的實(shí)例變量,那這個(gè)問題的答案就是不能,因?yàn)榉诸惒荒転轭愒黾宇~外的實(shí)例變量。
- 如果屬性只是一個(gè)存取方法以及存儲(chǔ)值的容器的集合,那么分類可以實(shí)現(xiàn)屬性。 分類中對屬性的實(shí)現(xiàn)其實(shí)只是實(shí)現(xiàn)了一個(gè)看起來像屬性的接口而已。
推薦??:
如果你想一起進(jìn)階,不妨添加一下交流群642363427