iOS AssociatedObject 底层实现原理

前言

使用 Category 为已经存在的类添加方法是我们很熟悉的常规操作,但是如果在 Category 中为类添加属性 @property ,则编译器会立即给我们如下警告:

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

提示我们需要手动为属性添加 setter gettr 方法或者使用 @dynamic 在运行时实现这些方法。 即明确的告诉我们在分类中 @property 并不会自动生成实例变量以及存取方法。

不是说好的使用 @property ,编译器会自动帮我们生成实例变量和对应的 settergetter 方法吗,此机制只能在类定义中实现,因为在分类中,类的实例变量的布局已经固定,使用 @property 已经无法向固定的布局中添加新的实例变量,所以我们需要使用关联对象以及两个方法来模拟构成属性的三个要素。

示例代码:

#import "HMObject.h"

NS_ASSUME_NONNULL_BEGIN

@interface HMObject (category)
// 在分类中添加一个属性
@property (nonatomic, copy) NSString *categoryProperty;
@end

NS_ASSUME_NONNULL_END
#import "HMObject+category.h"
#import <objc/runtime.h>

@implementation HMObject (category)

- (NSString *)categoryProperty {
    // _cmd 代指当前方法的选择子,即 @selector(categoryProperty)
    return objc_getAssociatedObject(self, _cmd);
}

- (void)setCategoryProperty:(NSString *)categoryProperty {
    objc_setAssociatedObject(self,
                             @selector(categoryProperty),
                             categoryProperty,
                             OBJC_ASSOCIATION_COPY_NONATOMIC);
}

@end

此时我们可以使用关联对象 Associated Object 来手动为 categoryProperty 添加存取方法,接下来我们对示例代码一步一步进行分析。

在类定义中使用 @property

在类定义中我们使用 @property 为类添加属性,如果不使用 @dynamic 标识该属性的话,编译器会自动帮我们生成一个名字为下划线加属性名的实例变量和该属性的 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

编译器会自动帮我们做如下三件事:

  1. 添加实例变量 _cusProperty
  2. 添加 setter 方法 setCusProperty
  3. 添加 getter 方法 cusProperty

即如下 HMObject.m 代码实现:

#import "HMObject.h"

@implementation HMObject
//@dynamic cusProperty;
{
    NSString *_cusProperty;
}

- (void)setCusProperty:(NSString *)cusProperty {
    _cusProperty = cusProperty;
}

- (NSString *)cusProperty {
    return _cusProperty;
}

@end

验证 @property

下面我们通过 LLDB 进行验证,首先我们把 HMObject.m 的代码都注释掉,只留下 HMObject.h 中的 cusProperty 属性。 然后在 main 函数中编写如下代码:

Class cls = NSClassFromString(@"HMObject");
NSLog(@"%@", cls); // :arrow_left: 这里打一个断点

开始验证:

这里我们也可以使用 runtimeclass_copyPropertyListclass_copyMethodListclass_copyIvarList 三个函数来分别获取 HMObject 的属性列表、方法列表和成员变量列表来验证编译器为我们自动生成了什么内容,但是这里我们采用一种更为简单的方法,仅通过控制台打印即可验证。

  1. 找到 clsbits
(lldb) x/5gx cls
0x1000022e8: 0x00000001000022c0 (isa) 0x00000001003ee140 (superclass)
0x1000022f8: 0x00000001003e84a0 0x0000001c00000000 (cache_t)
0x100002308: 0x0000000101850640 (bits)
  1. 强制转换 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 内容
(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 // 遵循协议为空
  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 个成员变量
    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")
  }
}

看到只有一个名字是 cusProperty 的属性,属性的 attributes 是: "T@\"NSString\",C,N,V_cusProperty"

|code|meaning| |...|...| |T|类型| |C|copy| |N|nonatomic| |V|实例变量|

  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 个 method
    first = {
      // 第一个正是 cusProperty 的 getter 函数
      name = "cusProperty"
      types = 0x0000000100000f79 "@16@0:8"
      imp = 0x0000000100000c30 (KCObjcTest`-[HMObject cusProperty])
    }
  }
}

看到方法的 TypeEncoding 如下: types = 0x0000000100000f79 "@16@0:8" 从左向右分别表示的含义是: @ 表示返回类型是 OC 对象,16 表示所有参数总长度,再往后 @ 表示第一个参数的类型,对应函数调用的 self 类型,0 表示从第 0 位开始,分隔号 : 表示第二个参数类型,对应 SEL ,8 表示从第 8 位开始,因为前面的一个参数 self 占 8 个字节。下面开始是自定义参数,因为 getter 函数没有自定义函数,所以只有 selfSEL 参数就结束了。 对应的函数原型正是 objc_msgSend 函数:

void
objc_msgSend(void /* id self, SEL op, ... */ )
  1. 打印剩下的两个 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])
}

看到一个是 cusPropertysetter 函数,一个是 C++ 的析构函数。

为了做出对比,我们注释掉 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) 

可看到 ivarsbasePropertiesbaseMethodList 都是 0x0000000000000000 ,即编译器没有为 HMObject 生成属性、成员变量和函数。 至此 @property 的作用可得到完整证明。

作为一个开发者,有一个学习的氛围跟一个交流圈子特别重要,这有个iOS交流群:642363427,不管你是小白还是大牛欢迎入驻 ,分享BAT,阿里面试题、面试经验,讨论技术,iOS开发者一起交流学习成长!

@property 能够为我们自动生成实例变量以及存取方法,而这三者构成了属性这个类似于语法糖的概念,为我们提供了更便利的点语法来访问属性:

self.property 等价于 [self property]; self.property = value; 等价于 [self setProperty:value];

习惯于 C/C++ 结构体和结构体指针取结构体成员变量时使用 .-> 。初见 OC 的点语法时有一丝疑问, self 明明是一个指针,访问它的成员变量时为什么用 . 呢?如果按 C/C++ 的规则,不是应该使用 self->_property 吗?

这里我们应与 C/C++ 的点语法做出区别理解, OC 中点语法是用来帮助我们便捷访问属性的,在类内部我们可以使用 _proertyself->_properyself.property 三种方式访问同一个成员变量,区别在于使用 self.property 时是通过调用 propertysettergetter 来读取成员变量,而前两种则是直接读取,因此当我们重写属性的 settergetter 并在内部做一些自定义操作时,我们一定要记得使用 self.property 来访问属性。

Associated Object

我们使用 objc_setAssociatedObjectobjc_getAssociatedObject 来分别模拟属性的存取方法,而使用关联对象模拟实例变量。 runtime.h 中定义了如下三个与关联对象相关的函数接口:

/** 
 * Sets an associated value for a given object using a given key and association policy.
 * 使用给定的键和关联策略为给定的对象设置关联的值。
 * 
 * @param object The source object for the association.
 * 关联的源对象
 *
 * @param key The key for the association.
 * 关联的 key
 * @param value The value to associate with the key key for object. 
 * Pass nil to clear an existing association.
 * 与对象的键相关联的值。传递 nil 以清除现有的关联。
 *
 * @param policy The policy for the association. For possible values, see “Associative Object Behaviors.”
 * 关联策略
 * 
 * @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.
 * 返回与给定键的给定对象关联的值
 * 
 * @param object The source object for the association.
 * 关联的源对象
 * @param key The key for the association.
 * 关联的 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.
 * 删除给定对象的所有关联
 * 
 * @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.
 *
 * 意指此函数会一下删除对象全部的关联对象,如果我们想要删除指定的关联对象,
 * 应该使用 objc_setAssociatedObject 函数把 value 参数传递 nil 即可。
 *
 * 此功能的主要目的是使对象轻松返回“原始状态”,因此不应从该对象中普遍删除关联,
 * 因为它还会删除其他 clients 可能已添加到该对象的关联。
 * 通常,您应该将 objc_setAssociatedObject 与 nil 一起使用以清除指定关联。
 * 
 * @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

存取函数中的参数 key 我们都使用了 @selector(categoryProperty) ,其实也可以使用静态指针 static void * 类型的参数来代替,不过这里强烈建议使用 @selector(categoryProperty) 作为 key 传入,因为这种方法省略了声明参数的代码,并且能很好地保证 key 的唯一性。

objc_AssociationPolicy policy

policy 代表关联策略:

/**
 * 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          
};

注释已经解释的很清楚了,即不同的策略对应不同的修饰符: | 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 文件包含了所有的核心操作,首先来分析相关的数据结构。

ObjcAssociation

associated object 机制中用于保存 关联策略关联值

class ObjcAssociation {
    // typedef unsigned long uintptr_t;
    uintptr_t _policy; // 关联策略
    id _value; // 关联值
public:
    // 构造函数,初始化列表初始化 policy 和 value
    ObjcAssociation(uintptr_t policy, id value) : _policy(policy), _value(value) {}
    // 构造函数,初始化列表,policy 初始化为 0, value 初始化为 nil 
    ObjcAssociation() : _policy(0), _value(nil) {}

    // 复制构造函数采用默认
    ObjcAssociation(const ObjcAssociation &other) = default;
    // 赋值操作符采用默认
    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);
    }

    // 内联函数获取 _policy
    inline uintptr_t policy() const { return _policy; }
    // 内联函数获取 _value
    inline id value() const { return _value; }

    // 在 SETTER 时使用:判断是否需要持有 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 时使用:与上面的 acquireValue 函数对应,释放旧值 value 
    inline void releaseHeldValue() {
        if (_value && (_policy & OBJC_ASSOCIATION_SETTER_RETAIN)) {
            // release 减少引用计数
            objc_release(_value);
        }
    }

    // 在 GETTER 时使用:根据关联策略判断是否对关联值进行 retain 操作
    inline void retainReturnedValue() {
        if (_value && (_policy & OBJC_ASSOCIATION_GETTER_RETAIN)) {
            objc_retain(_value);
        }
    }

    // 在 GETTER 时使用:判断是否需要放进自动释放池
    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 理解为一个 keyconst void * valueObjcAssociation 的哈希表即可。

AssociationsHashMap

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

同上,把 AssociationsHashMap 理解为一个 keyDisguisedPtr<objc_object> valueObjectAssociationMap 的哈希表即可。 DisguisedPtr<objc_object> 可理解为把 objc_object 地址伪装为一个整数。

AssociationsManager

AssociationsManager 的类定义不复杂,从数据结构角度来看的话它是作为一个 keyDisguisedPtr<objc_object> valueObjectAssociationMap 的哈希表来用的,这么看它好像和上面的 AssociationsHashMap 有些重合,其实它内部正是存储了一个局部静态的 AssociationsHashMap 用来存储程序中所有的关联对象。

AssociationsManagerLock

spinlock_t AssociationsManagerLock;

一个全局的自旋锁(互斥锁),保证 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>;
    // 静态变量 _mapStoreage,用于存储 AssociationsHashMap 数据
    static Storage _mapStorage;

public:
    // 构造函数 获取全局的 AssociationsManagerLock 加锁
    AssociationsManager()   { AssociationsManagerLock.lock(); }
    // 析构函数 AssociationsManagerLock 解锁
    ~AssociationsManager()  { AssociationsManagerLock.unlock(); }

    // 返回内部的保存的 AssociationsHashMap,
    AssociationsHashMap &get() {
        return _mapStorage.get();
    }

    // init 初始化函数实现只是 调用 Storage 的 init 函数
    static void init() {
        _mapStorage.init();
    }
};

// 其实这里有点想不明白,明明 AssociationsManager 已经定义了公开函数 get 获取内部 _mapStorage 的数据,

// 为什么这里在类定义外面还写了这句代码 ?
AssociationsManager::Storage AssociationsManager::_mapStorage;

管理 AssociationsHashMap 静态变量。

总结:

  1. 通过 AssociationsManagerget 函数取得一个全局唯一 AssociationsHashMap
  2. 根据我们的原始对象的 DisguisedPtr<objc_object>AssociationsHashMap 取得 ObjectAssociationMap
  3. 根据我们指定的关联 key ( const void *key ) 从 ObjectAssociationMap 取得 ObjcAssociation
  4. ObjcAssociation 的两个成员变量,保存我们的关联策略 _policy 和关联值 _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 设置对象的 uintptr_t has_assoc : 1; 位,标记该对象有关联对象,该对象 dealloc 时要进行清理工作。

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; // 判空对象和关联值都为 nil 则 return

    // 判断该类是否允许关联对象
    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};
    // 根据入参创建一个 association
    ObjcAssociation association{policy, value};

    // retain the new value (if any) outside the lock.
    // 在加锁之前根据关联策略判断是否 retain/copy 入参 value 
    association.acquireValue();

    {
        // 创建 mananger 临时变量
        // 这里还有一步连带操作
        // 在其构造函数中 AssociationsManagerLock.lock() 加锁
        AssociationsManager manager;
        // 取得全局的 AssociationsHashMap
        AssociationsHashMap &associations(manager.get());

        if (value) {
            // 这里 DenseMap 对我们而言是一个黑盒,这里只要看 try_emplace 函数

            // 在全局 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
                // 设置 uintptr_t has_assoc : 1; 位,标记该对象存在关联对象 
                object->setHasAssociatedObjects();
            }

            /* establish or replace the association */
            // 重建或者替换 association
            auto &refs = refs_result.first->second;

            // 这里存在一个疑问,如果值对象第一关联新值,且是 strong 强引用对象,
            // 如果 association 里面一直存放的就是新值新策略,那执行到函数结尾岂不是要执行 release 操作了 ?
            auto result = refs.try_emplace(key, std::move(association));
            if (!result.second) {
                // 替换
                // 如果之前有旧值的话把旧值的成员变量交换到 association
                // 然后在 函数执行结束时把旧值根据对应的策略判断执行 release
                association.swap(result.first->second);
            }
        } else {
            // value 为 nil 的情况,表示要把之前的关联对象置为 nil
            // 也可理解为移除指定的关联对象
            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);
                    // 清除指定的关联对象
                    refs.erase(it);
                    // 如果当前 object 的关联对象为空了,则同时从全局的 AssociationsHashMap
                    // 中移除该对象
                    if (refs.size() == 0) {
                        associations.erase(refs_it);
                    }
                }
            }
        }

        // 析构 mananger 临时变量
        // 这里还有一步连带操作
        // 在其析构函数中 AssociationsManagerLock.unlock() 解锁
    }

    // release the old value (outside of the lock).
    // 开始时 retain 的是新入参的 value, 这里释放的是旧值,association 内部的 value 已经被替换了
    association.releaseHeldValue();
}

函数执行过程中有两种情况:

value != nil
value == nil

函数流程图:

如果看通了上面的 _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 中取得对象对应的 ObjectAssociationMap
        AssociationsHashMap::iterator i = associations.find((objc_object *)object);
        if (i != associations.end()) {
            // 如果存在
            ObjectAssociationMap &refs = i->second;
            // 从 ObjectAssocationMap 中取得 key 对应的 ObjcAssociation 
            ObjectAssociationMap::iterator j = refs.find(key);
            if (j != refs.end()) {
                // 如果存在
                association = j->second;
                // 根据关联策略判断是否需要对 _value 执行 retain 操作
                association.retainReturnedValue();
            }
        }

        // 解锁
    }
    // 返回 _value 并根据关联策略判断是否需要放入自动释放池
    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 标记为 true,表示该对象有关联对象
    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 关联引用不同,此函数对性能敏感,
// 因为原始的 isa 对象(例如 OS 对象)无法跟踪它们是否具有关联的对象。
void
_object_remove_assocations(id object)
{
    // 对象对应的 ObjectAssociationMap
    ObjectAssociationMap refs{};

    {
        // 加锁
        AssociationsManager manager;
        // 取得全局的 AssociationsHashMap
        AssociationsHashMap &associations(manager.get());

        // 取得对象的对应 ObjectAssociationMap,里面包含所有的 (key, ObjcAssociation)
        AssociationsHashMap::iterator i = associations.find((objc_object *)object);
        if (i != associations.end()) {
            // 把 i->second 的内容都转入 refs 对象中
            refs.swap(i->second);
            // 从全局 AssociationsHashMap 移除对象的 ObjectAssociationMap
            associations.erase(i);
        }

        // 解锁
    }

    // release everything (outside of the lock).
    // 遍历对象的 ObjectAssociationMap 中的 (key, ObjcAssociation)
    // 对 ObjcAssociation 的 _value 根据 _policy 进行释放
    for (auto &i: refs) {
        i.second.releaseHeldValue();
    }
}

关联对象的本质

在分类中到底能否实现属性?首先要知道属性是什么,属性的概念决定了这个问题的答案。

  • 如果把属性理解为通过方法访问的实例变量,那这个问题的答案就是不能,因为分类不能为类增加额外的实例变量。
  • 如果属性只是一个存取方法以及存储值的容器的集合,那么分类可以实现属性。 分类中对属性的实现其实只是实现了一个看起来像属性的接口而已。

推荐👇:

如果你想一起进阶,不妨添加一下交流群642363427

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342

推荐阅读更多精彩内容