iOS AssociatedObject 底層實(shí)現(xiàn)原理

前言

使用 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)的 settergetter 方法嗎,此機(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í)例變量和該屬性的 settergetter 方法。我們編寫如下代碼:

// .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)幫我們做如下三件事:

  1. 添加實(shí)例變量 _cusProperty
  2. 添加 setter 方法 setCusProperty
  3. 添加 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)證:

這里我們也可以使用 runtimeclass_copyPropertyListclass_copyMethodList 、 class_copyIvarList 三個(gè)函數(shù)來分別獲取 HMObject 的屬性列表、方法列表和成員變量列表來驗(yàn)證編譯器為我們自動(dòng)生成了什么內(nèi)容,但是這里我們采用一種更為簡單的方法,僅通過控制臺(tái)打印即可驗(yàn)證。

  1. 找到 clsbits
(lldb) x/5gx cls
0x1000022e8: 0x00000001000022c0 (isa) 0x00000001003ee140 (superclass)
0x1000022f8: 0x00000001003e84a0 0x0000001c00000000 (cache_t)
0x100002308: 0x0000000101850640 (bits)
  1. 強(qiáng)制轉(zhuǎn)換 class_data_bits_t 指針
(lldb) p (class_data_bits_t *)0x100002308
(class_data_bits_t *) $1 = 0x0000000100002308
  1. 取得 class_rw_t *
(lldb) p $1->data()
(class_rw_t *) $2 = 0x0000000101850640
  1. 取得 class_ro_t *
(lldb) p $2->ro
(const class_ro_t *) $3 = 0x0000000100002128
  1. 打印 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 = {}
}
  1. 打印 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
    }
  }
}
  1. 打印 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í)例變量|

  1. 打印 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ù),所以只有 selfSEL 參數(shù)就結(jié)束了。 對應(yīng)的函數(shù)原型正是 objc_msgSend 函數(shù):

void
objc_msgSend(void /* id self, SEL op, ... */ )
  1. 打印剩下的兩個(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è)是 cusPropertysetter 函數(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 、 basePropertiesbaseMethodList 都是 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)部我們可以使用 _proertyself->_properyself.property 三種方式訪問同一個(gè)成員變量,區(qū)別在于使用 self.property 時(shí)是通過調(diào)用 propertysettergetter 來讀取成員變量,而前兩種則是直接讀取,因此當(dāng)我們重寫屬性的 settergetter 并在內(nèi)部做一些自定義操作時(shí),我們一定要記得使用 self.property 來訪問屬性。

Associated Object

我們使用 objc_setAssociatedObjectobjc_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è) keyconst void * valueObjcAssociation 的哈希表即可。

AssociationsHashMap

typedef DenseMap<DisguisedPtr<objc_object>, ObjectAssociationMap> AssociationsHashMap;

同上,把 AssociationsHashMap 理解為一個(gè) keyDisguisedPtr<objc_object> valueObjectAssociationMap 的哈希表即可。 DisguisedPtr<objc_object> 可理解為把 objc_object 地址偽裝為一個(gè)整數(shù)。

AssociationsManager

AssociationsManager 的類定義不復(fù)雜,從數(shù)據(jù)結(jié)構(gòu)角度來看的話它是作為一個(gè) keyDisguisedPtr<objc_object> valueObjectAssociationMap 的哈希表來用的,這么看它好像和上面的 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é):

  1. 通過 AssociationsManagerget 函數(shù)取得一個(gè)全局唯一 AssociationsHashMap 。
  2. 根據(jù)我們的原始對象的 DisguisedPtr<objc_object>AssociationsHashMap 取得 ObjectAssociationMap 。
  3. 根據(jù)我們指定的關(guān)聯(lián) key ( const void *key ) 從 ObjectAssociationMap 取得 ObjcAssociation 。
  4. 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

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容