OC底层原理(十四)类扩展与关联对象

6,710 阅读28分钟

一、类扩展

  1. 类扩展extension在我们的开发过程中其实经常使用的,下图红框部分就是一个ViewController的类扩展。
  • 图: 截屏2023-03-06 下午3.21.25.png
  1. 类扩展实际上是一个特殊的分类,也称作匿名分类,创建的类扩展只有.h文件,没有.m文件。
  • 如下图所示: 截屏2023-03-06 下午3.26.44.png 截屏2023-03-06 下午3.27.52.png
  1. 如果不通过创建文件的方式,类扩展的代码只能写在类声明与类实现之间,跟ViewController一样。

分类与类扩展的区别

  1. category:类别,分类
  • ①. 专门给类添加新的方法

  • ②. 不能给类添加成员属性,即使添加了成员变量,也无法获取。

  • ③. 可以通过runtime给分类添加属性

  • ④. 分类中用@property定义变量,只会生成变量的gettersetter方法的声明生成方法实现和带下划线的成员变量

  1. extension类扩展
  • ①. 可以说成是特殊的分类,也称作匿名分类

  • ②. 可以给类添加成员属性,但是是私有变量

  • ③. 可以给类添加方法,也是私有方法

验证extension扩展的加载方式

声明一个类CJStudent,为其添加extension扩展的属性与方法。

  • 自定义源码:
// 自定义类
@interface CJStudent : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) NSInteger number;
+ (void)classMethod;
- (void)instanceMethod;
@end

// 类扩展
@interface CJStudent()

@property (nonatomic, copy) NSString *ext_name;
@property (nonatomic, assign) NSInteger ext_number;
+ (void)ext_classMethod;
- (void)ext_InstanceMethod;

@end

@implementation CJStudent
+ (void)load {
    NSLog(@"%s", __func__);
}

- (void)instanceMethod {
    NSLog(@"%s", __func__);
}

+ (void)classMethod {
    NSLog(@"%s", __func__);
}

- (void)ext_InstanceMethod {
    NSLog(@"%s", __func__);
}

+ (void)ext_classMethod {
    NSLog(@"%s", __func__);
}

@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
         CJStudent *student = [SJStudent alloc];
    }
    return 0;
}

  1. 在终端使用指令xcrun -sdk iphonesimulator clang -arch arm64 -rewrite-objc main.m -o main-arm64.cppOC源码转化为c++代码:
  • c++代码:
struct CJStudent_IMPL {
	struct NSObject_IMPL NSObject_IVARS;
	NSString *_name;
	NSInteger _number;
	NSString *_ext_name;
	NSInteger _ext_number;
};
static struct /*_method_list_t*/ {
	unsigned int entsize;  // sizeof(struct _objc_method)
	unsigned int method_count;
	struct _objc_method method_list[18];
} _OBJC_$_INSTANCE_METHODS_CJStudent __attribute__ ((used, section ("__DATA,__objc_const"))) = {
	sizeof(_objc_method),
	18,
	{{(struct objc_selector *)"instanceMethod", "v16@0:8", (void *)_I_CJStudent_instanceMethod},
	{(struct objc_selector *)"ext_InstanceMethod", "v16@0:8", (void *)_I_CJStudent_ext_InstanceMethod},
	{(struct objc_selector *)"name", "@16@0:8", (void *)_I_CJStudent_name},
	{(struct objc_selector *)"setName:", "v24@0:8@16", (void *)_I_CJStudent_setName_},
	{(struct objc_selector *)"number", "q16@0:8", (void *)_I_CJStudent_number},
	{(struct objc_selector *)"setNumber:", "v24@0:8q16", (void *)_I_CJStudent_setNumber_},
	{(struct objc_selector *)"ext_name", "@16@0:8", (void *)_I_CJStudent_ext_name},
	{(struct objc_selector *)"setExt_name:", "v24@0:8@16", (void *)_I_CJStudent_setExt_name_},
	{(struct objc_selector *)"ext_number", "q16@0:8", (void *)_I_CJStudent_ext_number},
	{(struct objc_selector *)"setExt_number:", "v24@0:8q16", (void *)_I_CJStudent_setExt_number_},
	{(struct objc_selector *)"name", "@16@0:8", (void *)_I_CJStudent_name},
	{(struct objc_selector *)"setName:", "v24@0:8@16", (void *)_I_CJStudent_setName_},
	{(struct objc_selector *)"number", "q16@0:8", (void *)_I_CJStudent_number},
	{(struct objc_selector *)"setNumber:", "v24@0:8q16", (void *)_I_CJStudent_setNumber_},
	{(struct objc_selector *)"ext_name", "@16@0:8", (void *)_I_CJStudent_ext_name},
	{(struct objc_selector *)"setExt_name:", "v24@0:8@16", (void *)_I_CJStudent_setExt_name_},
	{(struct objc_selector *)"ext_number", "q16@0:8", (void *)_I_CJStudent_ext_number},
	{(struct objc_selector *)"setExt_number:", "v24@0:8q16", (void *)_I_CJStudent_setExt_number_}}
};
  1. 在可编译的objc4的源码的realizeClassWithoutSwift函数里将"CJPerson"改成"CJStudent"就在可以在类加载过程,断点调试了。
  • 如下图: 截屏2023-03-14 下午1.46.36.png
  • 通过命令打印输出CJStudent类中所有的方法:
(lldb) x/6gx cls
0x100008800: 0x00000001000087d8 0x0000000100721140
0x100008810: 0x0000000100719cb0 0x0034000000000000
0x100008820: 0x8000600000238080 0x00000001007210f0
(lldb) p (class_data_bits_t *)0x100008820
(class_data_bits_t *) $1 = 0x0000000100008820
(lldb) p $1->data()
(class_rw_t *) $2 = 0x0000600000238080
(lldb) p *$2
(class_rw_t) $3 = {
  flags = 2148007936
  witness = 0
  ro_or_rw_ext = {
    std::__1::atomic<unsigned long> = {
      Value = 4295000696
    }
  }
  firstSubclass = nil
  nextSiblingClass = 0x0000000100721168
}
(lldb) p $3.ro()
(const class_ro_t *) $4 = 0x0000000100008278
(lldb) p *$4
(const class_ro_t) $5 = {
  flags = 388
  instanceStart = 8
  instanceSize = 40
  reserved = 0
   = {
    ivarLayout = 0x0000000100003d5b "\U00000001\U00000011"
    nonMetaclass = 0x0000000100003d5b
  }
  name = {
    std::__1::atomic<const char *> = "CJStudent" {
      Value = 0x0000000100003d51 "CJStudent"
    }
  }
  baseMethods = {
    ptr = 0x0000000100008098
  }
  baseProtocols = nil
  ivars = 0x00000001000081a8
  weakIvarLayout = 0x0000000000000000
  baseProperties = 0x0000000100008230
  _swiftMetadataInitializer_NEVER_USE = {}
}
(lldb) p $5.ivars
(const ivar_list_t *const) $6 = 0x00000001000081a8
(lldb) p *$6
(const ivar_list_t) $7 = {
  entsize_list_tt<ivar_t, ivar_list_t, 0, PointerModifierNop> = (entsizeAndFlags = 32, count = 4)
}
(lldb) p $7.get(0)
(ivar_t) $8 = {
  offset = 0x00000001000086d8
  name = 0x0000000100003e16 "_name"
  type = 0x0000000100003f97 "@\"NSString\""
  alignment_raw = 3
  size = 8
}
(lldb) p $7.get(1)
(ivar_t) $9 = {
  offset = 0x00000001000086e0
  name = 0x0000000100003e1c "_number"
  type = 0x0000000100003fa3 "q"
  alignment_raw = 3
  size = 8
}
(lldb) p $7.get(2)
(ivar_t) $10 = {
  offset = 0x00000001000086e8
  name = 0x0000000100003e24 "_ext_name"
  type = 0x0000000100003f97 "@\"NSString\""
  alignment_raw = 3
  size = 8
}
(lldb) p $7.get(3)
(ivar_t) $11 = {
  offset = 0x00000001000086f0
  name = 0x0000000100003e2e "_ext_number"
  type = 0x0000000100003fa3 "q"
  alignment_raw = 3
  size = 8
}
(lldb) p *$5.baseMethods.ptr
(method_list_t) $12 = {
  entsize_list_tt<method_t, method_list_t, 4294901763, method_t::pointer_modifier> = (entsizeAndFlags = 24, count = 11)
}
(lldb) p $12.get(0).big()
(method_t::big) $13 = {
  name = "instanceMethod"
  types = 0x0000000100003f69 "v16@0:8"
  imp = 0x0000000100003310 (KCObjcBuild`-[CJStudent instanceMethod] at main.m:46)
}
(lldb) p $12.get(1).big()
(method_t::big) $14 = {
  name = "ext_InstanceMethod"
  types = 0x0000000100003f69 "v16@0:8"
  imp = 0x0000000100003340 (KCObjcBuild`-[CJStudent ext_InstanceMethod] at main.m:54)
}
(lldb) p $12.get(2).big()
(method_t::big) $15 = {
  name = "name"
  types = 0x0000000100003f71 "@16@0:8"
  imp = 0x0000000100003370 (KCObjcBuild`-[CJStudent name] at main.m:26)
}
(lldb) p $12.get(3).big()
(method_t::big) $16 = {
  name = "setName:"
  types = 0x0000000100003f79 "v24@0:8@16"
  imp = 0x0000000100003390 (KCObjcBuild`-[CJStudent setName:] at main.m:26)
}
(lldb) p $12.get(4).big()
(method_t::big) $17 = {
  name = "number"
  types = 0x0000000100003f84 "q16@0:8"
  imp = 0x00000001000033c0 (KCObjcBuild`-[CJStudent number] at main.m:27)
}
(lldb) p $12.get(5).big()
(method_t::big) $18 = {
  name = "setNumber:"
  types = 0x0000000100003f8c "v24@0:8q16"
  imp = 0x00000001000033e0 (KCObjcBuild`-[CJStudent setNumber:] at main.m:27)
}
(lldb) p $12.get(6).big()
(method_t::big) $19 = {
  name = "ext_name"
  types = 0x0000000100003f71 "@16@0:8"
  imp = 0x0000000100003400 (KCObjcBuild`-[CJStudent ext_name] at main.m:34)
}
(lldb) p $12.get(7).big()
(method_t::big) $20 = {
  name = "setExt_name:"
  types = 0x0000000100003f79 "v24@0:8@16"
  imp = 0x0000000100003420 (KCObjcBuild`-[CJStudent setExt_name:] at main.m:34)
}
(lldb) p $12.get(8).big()
(method_t::big) $21 = {
  name = "ext_number"
  types = 0x0000000100003f84 "q16@0:8"
  imp = 0x0000000100003450 (KCObjcBuild`-[CJStudent ext_number] at main.m:35)
}
(lldb) p $12.get(9).big()
(method_t::big) $22 = {
  name = "setExt_number:"
  types = 0x0000000100003f8c "v24@0:8q16"
  imp = 0x0000000100003470 (KCObjcBuild`-[CJStudent setExt_number:] at main.m:35)
}
(lldb) p $12.get(10).big()
(method_t::big) $23 = {
  name = ".cxx_destruct"
  types = 0x0000000100003f69 "v16@0:8"
  imp = 0x0000000100003490 (KCObjcBuild`-[CJStudent .cxx_destruct] at main.m:41)
}
(lldb) 
  • 总结:

    通过打印结果,我们发现其实类扩展中的方法以及属性并不是像加载分类数据那样加载的,而是与主类中定义实现的方法以及属性的加载方式一样

二、关联对象

关联对象的基本使用

在我们日常开发的过程中,有时我们需要给系统或者某个库中的某个类添加属性,但是我们又无法改变源码,因此我们只能创建此类的一个分类,因为我们在分类中定义的属性实际上只能用作计算属性,但是如果想在这个类的实例对象中存取属性,可以使用runtimeAPI关联对象,这种方式并不是在类的属性列表添加新的属性,而是通过在全局的哈希表里两次哈希map的形式存取关联对象的值。

  • CJPerson的分类CA中想要关联一个CJPet对象,可以编写如下的代码:
// CJPerson类
@interface CJPerson : NSObject

@end

@implementation CJPerson

@end

// CJPerson+CA分类
@interface CJPerson (CA)

@property (nonatomic, copy) NSString *ca_name;
@property (nonatomic, assign) NSInteger ca_age;
@property (nonatomic, strong) CJPet *ca_pet;

@end

static NSString *kCAPet = @"pet";
static NSString *kCAName= @"name";
static NSString *kCAAge = @"age";

@implementation CJPerson (CA)

- (void)setCa_pet:(CJPet *)ca_array {
    objc_setAssociatedObject(self, (__bridge const void * _Nonnull)(kCAPet), ca_array, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

- (CJPet *)ca_pet{
    return objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(kCAPet));
}

- (void)setCa_name:(NSString *)ca_name {
    objc_setAssociatedObject(self, (__bridge const void * _Nonnull)(kCAName), ca_name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}

- (NSString *)ca_name {
    return objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(kCAName));
}

- (void)setCa_age:(NSInteger)ca_age {
    objc_setAssociatedObject(self, (__bridge const void * _Nonnull)(kCAAge), [NSNumber numberWithInteger:ca_age], OBJC_ASSOCIATION_ASSIGN);
}

- (NSInteger)ca_age {
    NSNumber *age = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(kCAAge));
    if(!age) {
        return 0;
    } else {
        return  [age integerValue];
    }
}

@end

// main
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        CJPerson * person = [CJPerson alloc];
      
        CJPet *dog = [CJPet new];
        dog.name = @"狗仔";
        dog.age = 1;
        person.ca_pet = dog;
        NSLog(@"pet:%@, age:%ld", person.ca_pet.name, person.ca_pet.age);
    }
    return 0;
}
  • 编译运行程序,打印信息如下所示:
2023-03-10 01:46:57.193890+0800 KCObjcBuild[6374:190428] pet:狗仔, age:1

关联对象实现原理

通过关联对象的基本使用之后,我们再来了解关联对象的工作原理。这里最表面就是知道关联对象的设值关联对象的取值

关联对象的外部与内部策略

  1. 而在这之前,得先知道关联对象的外部策略(即对外接口可查可用的枚举)。
  • objc中的关联对象策略objc_AssociationPolicy:
/**
 * Policies related to associative references.
 * These are options to objc_setAssociatedObject()
 */
typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) {
    OBJC_ASSOCIATION_ASSIGN = 0,           /**< Specifies a weak reference to the associated object. */
    OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, /**< Specifies a strong reference to the associated object. 
                                            *   The association is not made atomically. */
    OBJC_ASSOCIATION_COPY_NONATOMIC = 3,   /**< Specifies that the associated object is copied. 
                                            *   The association is not made atomically. */
    OBJC_ASSOCIATION_RETAIN = 01401,       /**< Specifies a strong reference to the associated object.
                                            *   The association is made atomically. */
    OBJC_ASSOCIATION_COPY = 01403          /**< Specifies that the associated object is copied.
                                            *   The association is made atomically. */
};
  • 总结:

    objc_AssociationPolicy这个枚举可知,这五种策略,刚好对应了设置类的属性property的修饰词。 | objc_AssociationPolicy策略 | 属性property修饰词 | | --- | --- | | OBJC_ASSOCIATION_ASSIGN | @property (assign) | | OBJC_ASSOCIATION_RETAIN_NONATOMIC | @property (nonatomic, strong) | | OBJC_ASSOCIATION_COPY_NONATOMIC | @property (nonatomic, copy) | | OBJC_ASSOCIATION_RETAIN | @property (atomic, strong) | | OBJC_ASSOCIATION_COPY | @property (atomic, copy) |

  1. 而在关联对象c++源码内部的枚举却有所不同,它是区分settergetter情况的?
  • 内部策略枚举的c++源码:
enum {
    OBJC_ASSOCIATION_SETTER_ASSIGN      = 0,
    OBJC_ASSOCIATION_SETTER_RETAIN      = 1,
    OBJC_ASSOCIATION_SETTER_COPY        = 3,            // NOTE:  both bits are set, so we can simply test 1 bit in releaseValue below.
    OBJC_ASSOCIATION_GETTER_READ        = (0 << 8),
    OBJC_ASSOCIATION_GETTER_RETAIN      = (1 << 8),
    OBJC_ASSOCIATION_GETTER_AUTORELEASE = (2 << 8),
    OBJC_ASSOCIATION_SYSTEM_OBJECT      = _OBJC_ASSOCIATION_SYSTEM_OBJECT, // 1 << 16
};
  • 结论:

    关联的策略外部枚举与外部枚举对比可知:

    • ①. 外部策略枚举设置值刚好对应内部枚举前3个值。即外部策略枚举objc_AssociationPolicy就只能直接影响内部策略枚举settter部分。

    • ②. 而getter部分暂时没有进行深入验证分析,但是通过下文对关联对象设值时,对关联对象的引用计数器的控制。可知settergetter是对关联对象设值与取值时,是否需要引用计数器变化,或者延时释放autorelease。这部分留到分析iOS内存管理时解析。

关联对象的设值

从上面我们已经先了解内部与外部的关联对象策略的区别,但是要探究一下关联对象的实现原理,就要先要查看objc_setAssociatedObject关联对象设值函数,发现它主要功能还是在_object_set_associative_reference函数里。

  • 其源码如下:
// 设置关联对象
void
objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
{
    _object_set_associative_reference(object, key, value, policy);
}

void
_object_set_associative_reference(id object, const void *key, id value, uintptr_t policy)
{
    // 当为对象和键传递nil时,这段代码一直有效。
    // 一些代码可能依赖于此而不会崩溃。检查并显式处理它。
    // rdar://problem/44094390
    // 被关联对象为空或者关联值为空,直接返回
    if (!object && !value) return;

    // isa有一位信息为禁止关联对象,如果这个被关联对象禁用类关联对象,抛出错误
    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));

    // 将当前被关联对象包装成DisguisedPtr类型,就是对象进行正负取反加密
    DisguisedPtr<objc_object> disguised{(objc_object *)object};
    // ObjcAssociation是对关联对象的值以及关联策略的C++类包装类型
    ObjcAssociation association{policy, value};

    // 在锁外retain保留新值(如果有)。
    // 持有关联对象,设置属性信息
    association.acquireValue();

    bool isFirstAssociation = false;
    {
        // 调用构造函数,构造函数内加锁操作
        AssociationsManager manager;
        // 获取全局的HashMap
        AssociationsHashMap &associations(manager.get());

        // 如果值不为空
        if (value) {
            // 去关联对象表中找对象对应的二级表,如果没有内部会重新生成一个
            auto refs_result = associations.try_emplace(disguised, ObjectAssociationMap{});
            // 如果没有找到
            if (refs_result.second) {
                /* it's the first association we make */
                // 说明是第一次设置关联对象,把是否关联对象设置为YES
                isFirstAssociation = true;
            }

            /* establish or replace the association */
            auto &refs = refs_result.first->second;
            // 在二级表中找key对应的内容
            auto result = refs.try_emplace(key, std::move(association));
            // 如果已经有内容了,没有内容上面association已经插入了值,所以啥也不用干
            if (!result.second) {
                // 替换掉
                association.swap(result.first->second);
            }
        // 如果value为空
        } else {
            // 通过被伪装的对象值找到对应的二级表
            auto refs_it = associations.find(disguised);
            // 如果有
            if (refs_it != associations.end()) {
                auto &refs = refs_it->second;
                // 通过key再在二级表里面找到对应内容
                auto it = refs.find(key);
                // 如果有
                if (it != refs.end()) {
                    // 删除掉
                    association.swap(it->second);
                    refs.erase(it);
                    if (refs.size() == 0) {
                        associations.erase(refs_it);

                    }
                }
            }
        }
    }

    // 在锁外部调用setHasAssociatedObjects,
    // 因为如果对象有_noteAssociatedObject方法,
    // 这将调用该方法,并且这可能会触发+initialize,这可能会执行任意操作,包括设置更多关联对象。
    if (isFirstAssociation)
        object->setHasAssociatedObjects();

    // release释放旧值(锁外)。
    association.releaseHeldValue();
}
  • 结论:

    objc_setAssociatedObject关联对象设置函数,发现它主要功能还是在_object_set_associative_reference函数里:

    • ①. 伪装被关联对象为DisguisedPtr,将关联对象的值以及关联策略封装成ObjcAssociation
    • ②. 通过关联对象管理者AssociationsManager找到全局关联对象表AssociationsHashMap,通过DisguisedPtr伪装对象在关联对象表中找对应的二级表。
    • ③. 通过key找对应ObjcAssociation,有就替换,没有加插入。

DisguisedPtr

// DisguisedPtr<T> acts like pointer type T*, except the 
// stored value is disguised to hide it from tools like `leaks`.
// nil is disguised as itself so zero-filled memory works as expected, 
// which means 0x80..00 is also disguised as itself but we don't care.
// Note that weak_entry_t knows about this encoding.
template <typename T>
class DisguisedPtr {
    uintptr_t value;

    static uintptr_t disguise(T* ptr) {
        return -(uintptr_t)ptr;
    }

    static T* undisguise(uintptr_t val) {
        return (T*)-val;
    }

 public:
    DisguisedPtr() { }
    DisguisedPtr(T* ptr) 
        : value(disguise(ptr)) { }
    DisguisedPtr(const DisguisedPtr<T>& ptr) 
        : value(ptr.value) { }

    DisguisedPtr<T>& operator = (T* rhs) {
        value = disguise(rhs);
        return *this;
    }
    DisguisedPtr<T>& operator = (const DisguisedPtr<T>& rhs) {
        value = rhs.value;
        return *this;
    }

    operator T* () const {
        return undisguise(value);
    }
    T* operator -> () const { 
        return undisguise(value);
    }
    T& operator * () const { 
        return *undisguise(value);
    }
    T& operator [] (size_t i) const {
        return undisguise(value)[i];
    }

    // pointer arithmetic operators omitted 
    // because we don't currently use them anywhere
};
  • 总结:

    _object_set_associative_reference函数中创建的disguised变量,调用了DisguisedPtr的构造函数,传入的是被关联对象的对象地址,经过disguise函数的伪装(指针值转换为十进制,指针值转换为负数)存储到disguised的成员变量value中。

ObjcAssociation

创建的association变量是ObjcAssociationC++类型,初始化调用了其构造函数,将关联对象以及关联策略分别存储到其成员变量_value以及_policy中保存起来。

  • c++类结构:
// 扩展的策略位。
// 由于设置关联对象的策略objc_AssociationPolicy只有5个,还不区分setter与getter情况
// 这里做了setter与getter的区分,主要是关联对象的引用计数器的情况区分。
// 我们自定义的关联对象一般只会处理按到前三个setter的策略
enum {
    OBJC_ASSOCIATION_SETTER_ASSIGN      = 0, 
    OBJC_ASSOCIATION_SETTER_RETAIN      = 1,
    OBJC_ASSOCIATION_SETTER_COPY        = 3,            // NOTE:  both bits are set, so we can simply test 1 bit in releaseValue below.
    OBJC_ASSOCIATION_GETTER_READ        = (0 << 8),
    OBJC_ASSOCIATION_GETTER_RETAIN      = (1 << 8),
    OBJC_ASSOCIATION_GETTER_AUTORELEASE = (2 << 8),
    OBJC_ASSOCIATION_SYSTEM_OBJECT      = _OBJC_ASSOCIATION_SYSTEM_OBJECT, // 1 << 16
};

spinlock_t AssociationsManagerLock;
namespace objc {
class ObjcAssociation {
    uintptr_t _policy; // 关联策略就是属性的修饰类型(atomic,nonatomic,assign,strong,copy)
    id _value; // 被关联的对象
public:
    ObjcAssociation(uintptr_t policy, id value) : _policy(policy), _value(value) {}
    ObjcAssociation() : _policy(0), _value(nil) {}
    ObjcAssociation(const ObjcAssociation &other) = default;
    ObjcAssociation &operator=(const ObjcAssociation &other) = default;
    ObjcAssociation(ObjcAssociation &&other) : ObjcAssociation() {
        swap(other);
    }

    inline void swap(ObjcAssociation &other) {
        std::swap(_policy, other._policy);
        std::swap(_value, other._value);
    }

    inline uintptr_t policy() const { return _policy; }
    inline id value() const { return _value; }
    // 重点:关联对象赋值时,实现引用计数器+1或者copy
    inline void acquireValue() {
        if (_value) {
            switch (_policy & 0xFF) {
            case OBJC_ASSOCIATION_SETTER_RETAIN:
                _value = objc_retain(_value);
                break;
            case OBJC_ASSOCIATION_SETTER_COPY:
                _value = ((id(*)(id, SEL))objc_msgSend)(_value, @selector(copy));
                break;
            }
        }
    }

    inline void releaseHeldValue() {
        if (_value && (_policy & OBJC_ASSOCIATION_SETTER_RETAIN)) {
            objc_release(_value);
        }
    }

    inline void retainReturnedValue() {
        if (_value && (_policy & OBJC_ASSOCIATION_GETTER_RETAIN)) {
            objc_retain(_value);
        }
    }

    inline id autoreleaseReturnedValue() {
        if (slowpath(_value && (_policy & OBJC_ASSOCIATION_GETTER_AUTORELEASE))) {
            return objc_autorelease(_value);
        }
        return _value;
    }
};
....
....
}
调试ObjcAssociationacquireValue

运行代码,在_object_set_associative_reference的源代码添加断点,然后association调用函数acquireValue持有此关联对象。

  • 如下所示: 截屏2023-03-13 上午2.00.39.png

  • 结论:

    acquireValue这个函数中会根据关联策略的不同,进行不同的存储如果关联策略是OBJC_ASSOCIATION_RETAIN或者OBJC_ASSOCIATION_RETAIN_NONATOMIC类型

    • ①. 就会使用objc_retain函数持有这个对象,然后这个对象的引用计数值就会加1,在objc_retain函数调用前后打印此对象isa的引用计数值。

      • 验证如下所示: 截屏2023-03-13 上午2.06.38.png

      • 结果:

        • 可以看到这个对象中isa中倒数第二位由2变成了3,也就是其引用计数的值增加了1,其实这也可以叫做浅拷贝
    • ③. 而OBJC_ASSOCIATION_SETTER_COPY则需要此对象所属类实现NSCopying协议中的copyWithZone实例方法,最终决定是深拷贝或者浅拷贝

    • ②. 但是如果关联值不是类对象,而是基本数据类型或者TaggedPointer,就是直接存储其值。

AssociationsManager

_object_set_associative_reference函数里定义了isFirstAssociation变量用来判断是不是首次关联,然后定义了一个C++类类型为AssociationsManager的变量manager

  • AssociationsManager这个类定义代码:
// class AssociationsManager manages a lock / hash table singleton pair.
// Allocating an instance acquires the lock

class AssociationsManager {
    using Storage = ExplicitInitDenseMap<DisguisedPtr<objc_object>, ObjectAssociationMap>;
    static Storage _mapStorage;

public:
    AssociationsManager()   { AssociationsManagerLock.lock(); }
    ~AssociationsManager()  { AssociationsManagerLock.unlock(); }

    AssociationsHashMap &get() {
        return _mapStorage.get();
    }

    static void init() {
        _mapStorage.init();
    }
};

AssociationsManager::Storage AssociationsManager::_mapStorage;
  • 结论:

    其中变量_mapStorageStorageExplicitInitDenseMap类的别名)类型的局部静态变量,也就是说_mapStorage只会被初始化一次,并且其生命周期为从初始化一直到应用程序运行结束,因此不管以后再创建多少个AssociationsManager类型变量,_mapStorage仅初始化一次并且一直存在,类似于一个单例

ExplicitInitDenseMap

ExplicitInitDenseMap这个C++类代码。

  • 如下所示:
// Convenience class for Dense Maps & Sets
template <typename Key, typename Value>
class ExplicitInitDenseMap : public ExplicitInit<DenseMap<Key, Value>> { };

// We cannot use a C++ static initializer to initialize certain globals because
// libc calls us before our C++ initializers run. We also don't want a global
// pointer to some globals because of the extra indirection.
//
// ExplicitInit / LazyInit wrap doing it the hard way.
template <typename Type>
class ExplicitInit {
    alignas(Type) uint8_t _storage[sizeof(Type)];

public:
    template <typename... Ts>
    void init(Ts &&... Args) {
        new (_storage) Type(std::forward<Ts>(Args)...);
    }

    Type &get() {
        return *reinterpret_cast<Type *>(_storage);
    }
};
  • 结论:

    代码很少,但是主要代码是在其父类ExplicitInit中,ExplicitInitDenseMap就是个hashmap.

ExplicitInit
  • 代码如下所示:
namespace objc {
// We cannot use a C++ static initializer to initialize certain globals because
// libc calls us before our C++ initializers run. We also don't want a global
// pointer to some globals because of the extra indirection.
//
// ExplicitInit / LazyInit wrap doing it the hard way.
template <typename Type>
class ExplicitInit {
   alignas(Type) uint8_t _storage[sizeof(Type)];

public:
   template <typename... Ts>
   void init(Ts &&... Args) {
       new (_storage) Type(std::forward<Ts>(Args)...);
   }

   Type &get() {
       return *reinterpret_cast<Type *>(_storage);
   }
};

  • 结论:

    ExplicitInit这个模板类中有一个uint8_t(无符号8位整型,1字节大小)类型的数组成员变量_storage,这个数组初始化大小为模板类传入的类型的大小。

AssociationsManager的作用

回到_object_set_associative_reference函数里,调用manager变量中的get函数获取到AssociationsHashMap类型的变量associations,而实际上associations是一个哈希表键值对分别为包装后的被关联对象类型DisguisedPtr以及存储这个被关联对象所关联对象信息的哈希表。

  • ①. 如果关联对象的值value不为空,首先就会调用associationstry_emplace函数尝试设置关联值哈希表中键值disguised所对应的值为一个空的ObjectAssociationMap,因为被关联对象在还未设置关联对象之前,是不会被加入到关联值哈希表中的。

  • ②. 如果是首次加入,还需要为其创建一个空的ObjectAssociationMap变量,实际上ObjectAssociationMap也是一个哈希表,是用来存储其关联对象信息,这两种类型的定义如下图所示: image.png

  • ③. 其实这两种类型都是属于DenseMap这个C++模板类,只不过它们对应的模板不同而已,DenseMap模板类中定义了如下图所示几个成员变量。 image.png

try_emplace

try_emplace函数代码如下所示:

class DenseMapBase {
...
 // Inserts key,value pair into the map if the key isn't already in the 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);
  }
...
}
  • 结论:

    • ①. 这个方法返回的类型是std::pair中的结构模板将2个数据组合成一个数据,将迭代器iterator与布尔值bool合并返回,通过firstsecond访问。 截屏2023-03-13 下午1.11.11.png
    • ②. iteratorDenseMapIterator迭代器;如果有内容返回对应的迭代器,如果没有的话,添加一个,并返回DenseMapIterator迭代器
DenseMapIterator
  • c++源码:
template <typename KeyT, typename ValueT, typename ValueInfoT,
          typename KeyInfoT, typename Bucket, bool IsConst>
class DenseMapIterator {
  friend class DenseMapIterator<KeyT, ValueT, ValueInfoT, KeyInfoT, Bucket, true>;
  friend class DenseMapIterator<KeyT, ValueT, ValueInfoT, KeyInfoT, Bucket, false>;

  using ConstIterator = DenseMapIterator<KeyT, ValueT, ValueInfoT, KeyInfoT, Bucket, true>;

public:
  using difference_type = ptrdiff_t;
  using value_type =
      typename std::conditional<IsConst, const Bucket, Bucket>::type;
  using pointer = value_type *;
  using reference = value_type &;
  using iterator_category = std::forward_iterator_tag;

private:
  pointer Ptr = nullptr;
  pointer End = nullptr;

public:
  DenseMapIterator() = default;

  DenseMapIterator(pointer Pos, pointer E,
                   bool NoAdvance = false)
      : Ptr(Pos), End(E) {
    if (NoAdvance) return;
    AdvancePastEmptyBuckets();
  }

  // 将ctor从非常数迭代器转换为常量迭代器。
  // SFINAE用于常量迭代器目的地,因此它不会最终成为用户定义的复制构造函数。
  template <bool IsConstSrc,
            typename = typename std::enable_if<!IsConstSrc && IsConst>::type>
  DenseMapIterator(
      const DenseMapIterator<KeyT, ValueT, ValueInfoT, KeyInfoT, Bucket, IsConstSrc> &I)
      : Ptr(I.Ptr), End(I.End) {}

  reference operator*() const {
    return *Ptr;
  }
  pointer operator->() const {
    return Ptr;
  }

  bool operator==(const ConstIterator &RHS) const {
    return Ptr == RHS.Ptr;
  }
  bool operator!=(const ConstIterator &RHS) const {
    return Ptr != RHS.Ptr;
  }

  inline DenseMapIterator& operator++() {  // Preincrement
    ++Ptr;
    AdvancePastEmptyBuckets();
    return *this;
  }
  DenseMapIterator operator++(int) {  // Postincrement
    DenseMapIterator tmp = *this; ++*this; return tmp;
  }

private:
  void AdvancePastEmptyBuckets() {
    ASSERT(Ptr <= End);
    const KeyT Empty = KeyInfoT::getEmptyKey();
    const KeyT Tombstone = KeyInfoT::getTombstoneKey();

    while (Ptr != End && (KeyInfoT::isEqual(Ptr->getFirst(), Empty) ||
                          KeyInfoT::isEqual(Ptr->getFirst(), Tombstone)))
      ++Ptr;
  }

  void RetreatPastEmptyBuckets() {
    ASSERT(Ptr >= End);
    const KeyT Empty = KeyInfoT::getEmptyKey();
    const KeyT Tombstone = KeyInfoT::getTombstoneKey();

    while (Ptr != End && (KeyInfoT::isEqual(Ptr[-1].getFirst(), Empty) ||
                          KeyInfoT::isEqual(Ptr[-1].getFirst(), Tombstone)))
      --Ptr;
  }
};

conditional是选择器

可以看到使用了两次try_emplace方法,可以得知他是嵌套两层的HashMap结构,根据上面代码的理解,可以得到以下结构图:

LookupBucketFor

可以看到在这个函数中会调用LookupBucketFor函数查找这个被关联对象在关联哈希表中是否存在对象关联哈希表,LookupBucketFor函数代码如下:

  • 第一个LookupBucketFor函数代码如下所示:
  /// LookupBucketFor-查找Val的适当bucket,并将其返回到FoundBucket中。
  /// 如果bucket包含键和值,则返回true,
  /// 否则返回带有空标记或墓碑的bucket,并返回false。
  template<typename LookupKeyT>
  bool LookupBucketFor(const LookupKeyT &Val,
                       const BucketT *&FoundBucket) const {
    // 获取首个bucket的地址指针
    const BucketT *BucketsPtr = getBuckets();
    // 获取buckets的数量
    const unsigned NumBuckets = getNumBuckets();

    if (NumBuckets == 0) {
      // 如果Buckets的数量为0,就是没有找到相应的bucket,直接返回false
      FoundBucket = nullptr;
      return false;
    }

    // FoundTombstone - Keep track of whether we find a tombstone while probing.
    const BucketT *FoundTombstone = nullptr;
    const KeyT EmptyKey = getEmptyKey();
    const KeyT TombstoneKey = getTombstoneKey();
    assert(!KeyInfoT::isEqual(Val, EmptyKey) &&
           !KeyInfoT::isEqual(Val, TombstoneKey) &&
           "Empty/Tombstone value shouldn't be inserted into map!");

    // 调用hash函数获取Val在buckets中的索引
    unsigned BucketNo = getHashValue(Val) & (NumBuckets-1);
    // hash探索次数
    unsigned ProbeAmt = 1;
    while (true) {
      // 获取对应索引位置的bucket
      const BucketT *ThisBucket = BucketsPtr + BucketNo;
      // Found Val's bucket?  If so, return it.
      if (LLVM_LIKELY(KeyInfoT::isEqual(Val, ThisBucket->getFirst()))) {
        // 如果val等于当前Bucket中的key值,就找到了bucket,返回true
        FoundBucket = ThisBucket;
        return true;
      }

      // If we found an empty bucket, the key doesn't exist in the set.
      // Insert it and return the default value.
      if (LLVM_LIKELY(KeyInfoT::isEqual(ThisBucket->getFirst(), EmptyKey))) {
        // 如果当前bucket的key值为空,说明Val代表key及其Value还未插入到buckets中,就直接返回false
        // If we've already seen a tombstone while probing, fill it in instead
        // of the empty bucket we eventually probed to.
        FoundBucket = FoundTombstone ? FoundTombstone : ThisBucket;
        return false;
      }

      // If this is a tombstone, remember it.  If Val ends up not in the map, we
      // prefer to return it than something that would require more probing.
      // Ditto for zero values.
      if (KeyInfoT::isEqual(ThisBucket->getFirst(), TombstoneKey) &&
          !FoundTombstone)
        FoundTombstone = ThisBucket;  // Remember the first tombstone found.
      if (ValueInfoT::isPurgeable(ThisBucket->getSecond())  &&  !FoundTombstone)
        FoundTombstone = ThisBucket;

      // Otherwise, it's a hash collision or a tombstone, continue quadratic
      // probing.
      if (ProbeAmt > NumBuckets) {
        // 如果hash次数大于Bucket的数量,都没有找到对应Bucket,出现错误
        FatalCorruptHashTables(BucketsPtr, NumBuckets);
      }
      // 再hash
      BucketNo += ProbeAmt++;
      BucketNo &= (NumBuckets-1);
    }
  }

  template <typename LookupKeyT>
  bool LookupBucketFor(const LookupKeyT &Val, BucketT *&FoundBucket) {
    const BucketT *ConstFoundBucket;
    bool Result = const_cast<const DenseMapBase *>(this)
      ->LookupBucketFor(Val, ConstFoundBucket);
    FoundBucket = const_cast<BucketT *>(ConstFoundBucket);
    return Result;
  }

  • 结论:

    这个函数中的代码逻辑是从Buckets中查找Val这个Key所对应的Bucket,首先获取到Buckets中的首个Bucket的首地址以及Buckets数量

      1. 如果Buckets数量为0,说明AssociationsHashMap为一个空的hash表,当然找不到任何Bucket,就直接返回false
      1. 如果不为空,就通过hash函数获取到Val这个Key值在Buckets所对应的存储位置
      • ①. 如果此位置的Bucket存在且Key值为Val,那么就说明之前已经创建过了这个Val所对应的ObjectAssociationMap,那么就获取这个位置的Bucket并返回true

      • ②. 如果此位置的Bucket为空,就说明还未插入Val作为KeyBucket,那么就获取这个位置的Bucket并返回false,获取到这个位置的Bucket是方便存值

      • ③. 如果此位置的Bucket存在但不等于Val对应KeyBucket,那么就产生了hash碰撞,那么就再hash获取,获取下一个Bucket再次进行以上判断,直到产生碰撞的次数大于了Buckets的数量,如果此时都未找到Val所对应的Bucket,那么就说明发生了错误。

InsertIntoBucketWithLookup

try_emplace调用完LookupBucketFor函数后,如果找到了Val所对应的Bucket,就会直接返回pair<iterator, bool>这种类似于元组的值,其中iterator实际上是一个名为DenseMapIteratorC++模板类,在try_emplace函数调用所返回的iterator类型的值中,Ptr是指向所找到对应ValBucket的指针,End是指向AssociationsHashMapBuckets中最后一个Bucket的指针,然后pari中第二个bool值设置为true是表示找到了,但是如果调用完LookupBucketFor也没有找到Val所对应的Bucket,就会调用InsertIntoBucket函数在Buckets中插入一个新值,也就是初始化Val所对应位置的Bucket

  • 其代码如下所示:
class DenseMapBase {
...
  template <typename LookupKeyT>
  BucketT *InsertIntoBucketWithLookup(BucketT *TheBucket, KeyT &&Key,
                                      ValueT &&Value, LookupKeyT &Lookup) {
    TheBucket = InsertIntoBucketImpl(Key, Lookup, TheBucket);

    TheBucket->getFirst() = std::move(Key);
    ::new (&TheBucket->getSecond()) ValueT(std::move(Value));
    return TheBucket;
  }
...
}
InsertIntoBucketImpl

而在这个函数中又调用了InsertIntoBucketImpl函数。

  • 代码如下所示:
template <typename LookupKeyT>
  BucketT *InsertIntoBucketImpl(const KeyT &Key, const LookupKeyT &Lookup,
                                BucketT *TheBucket) {
    // If the load of the hash table is more than 3/4, or if fewer than 1/8 of
    // the buckets are empty (meaning that many are filled with tombstones),
    // grow the table.
    //
    // The later case is tricky.  For example, if we had one empty bucket with
    // tons of tombstones, failing lookups (e.g. for insertion) would have to
    // probe almost the entire table until it found the empty bucket.  If the
    // table completely filled with tombstones, no lookup would ever succeed,
    // causing infinite loops in lookup.
    unsigned NewNumEntries = getNumEntries() + 1;
    unsigned NumBuckets = getNumBuckets();
    if (LLVM_UNLIKELY(NewNumEntries * 4 >= NumBuckets * 3)) {
      this->grow(NumBuckets * 2);
      LookupBucketFor(Lookup, TheBucket);
      NumBuckets = getNumBuckets();
    } else if (LLVM_UNLIKELY(NumBuckets-(NewNumEntries+getNumTombstones()) <=
                             NumBuckets/8)) {
      this->grow(NumBuckets);
      LookupBucketFor(Lookup, TheBucket);
    }
    ASSERT(TheBucket);

    // Only update the state after we've grown our bucket space appropriately
    // so that when growing buckets we have self-consistent entry count.
    // If we are writing over a tombstone or zero value, remember this.
    if (KeyInfoT::isEqual(TheBucket->getFirst(), getEmptyKey())) {
      // Replacing an empty bucket.
      incrementNumEntries();
    } else if (KeyInfoT::isEqual(TheBucket->getFirst(), getTombstoneKey())) {
      // Replacing a tombstone.
      incrementNumEntries();
      decrementNumTombstones();
    } else {
      // we should be purging a zero. No accounting changes.
      ASSERT(ValueInfoT::isPurgeable(TheBucket->getSecond()));
      TheBucket->getSecond().~ValueT();
    }

    return TheBucket;
  }
  • 结论:

    可以看到其实这个函数首先会判断是否需要对AssociationsHaseMapBuckets进行扩容:

    • 如果其存储容量超过了总容量的3/4(装载因子),就会调用grow函数创建一个容量为当前容量的两倍大小的hash表,并且将之前旧表中的Bucket添加到这个新的hash表中,然后根据传入的Key(此时也就是所包装的被关联对象)调用LookupBucketFor函数重新获取对应位置Bucket,然后Buckets总数量加1,返回这个Bucket,而在InsertIntoBucket函数中获取到这个Bucket之后,就会对这个BucketKey以及value进行赋值,将包装的被关联对象通过函数forward赋值给Key,将新创建的ObjectAssociationMap通过包装赋值给Value

setHasAssociatedObjects

接着回到_object_set_associative_reference函数中,在调用完try_emplace函数获取到refs_result这个变量后,会根据其第二个值判断被关联对象是否是第一次加入到AssociationsHaseMap中,如果是将之前的isFirstAssociation设置为true,然后获取到被关联对象的ObjectAssociationMap,也就是refs,如果此时是第一次为这个被关联对象添加关联对象,那么refs表肯定是个空表,此时调用refs的函数try_emplace获取传入的key所对应的关联对象信息(也就是ObjcAssociation包装了policy(关联策略)以及value(关联对象)的类型),并传入当前的association作为参数,此时refs调用try_emplace函数的逻辑是与associations调用try_emplace函数的逻辑是一样的,这里就不详细阐述了,但是另外有一点不同的是,如果refs调用try_emplace函数获取到Bucket是之前存在了的,就需要将association中的管理策略以及关联与refskey所对应的bucket中的valueObjcAssociation类型)管理策略以及关联对象分别进行交换。

然后又会判断isFirstAssociation是否为真,如果为真,就会调用被关联对象的setHasAssociatedObjects 截屏2023-03-13 下午2.04.04.png

  • c++源码:
inline void
objc_object::setHasAssociatedObjects()
{
    if (isTaggedPointer()) return;

    if (slowpath(!hasNonpointerIsa() && ISA()->hasCustomRR()) && !ISA()->isFuture() && !ISA()->isMetaClass()) {
        void(*setAssoc)(id, SEL) = (void(*)(id, SEL)) object_getMethodImplementation((id)this, @selector(_noteAssociatedObjects));
        if ((IMP)setAssoc != _objc_msgForward) {
            (*setAssoc)((id)this, @selector(_noteAssociatedObjects));
        }
    }

    isa_t newisa, oldisa = LoadExclusive(&isa().bits);
    do {
        newisa = oldisa;
        if (!newisa.nonpointer  ||  newisa.has_assoc) {
            ClearExclusive(&isa().bits);
            return;
        }
        newisa.has_assoc = true;
    } while (slowpath(!StoreExclusive(&isa().bits, &oldisa.bits, newisa.bits)));
}
  • 结论:

    setHasAssociatedObjects函数设置这个被关联对象的isa的值,将has_assoc这个段的数据设置为true,然后调用releaseHeldValue函数释放旧的association中的_value,这就是关联对象的设置流程。


单个关联对象的删除

如果你想要删除某个被关联对象的某个关联对象,只需要在调用objc_setAssociatedObject函数时将传入的value值设置为nil就可以了,而在_object_set_associative_reference函数中会判断value是否为空,如果value不为空,执行的就是关联对象的设值流程,如果value为空,执行的就是关联对象的删除流程。

  • 测试代码:
// main
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        CJPerson * person = [CJPerson alloc];
      
        CJPet *dog = [CJPet new];
        dog.name = @"狗仔";
        dog.age = 1;
        person.ca_pet = dog;
        NSLog(@"pet:%@, age:%ld", person.ca_pet.name, person.ca_pet.age);
        person.ca_pet = nil;
    }
    return 0;
}
  • _objc_set_associative_reference的删除关联对象的代码如下图: image.png

associations.find(disguised)

首先会调用associationsfind函数,找到被关联对象所对应的AssociationHashMap表,find函数代码如下所示:

 iterator find(const_arg_type_t<KeyT> Val) {
    BucketT *TheBucket;
    if (LookupBucketFor(Val, TheBucket))
      return makeIterator(TheBucket, getBucketsEnd(), true);
    return end();
  }
  • 结论:
      1. find函数中也是通过LookupBucketFor函数进行查找到,如果查找到了,就会返回iterator类型的变量。
      1. 如果查找不到就会返回调用end()函数之后的返回值。

associations.find(key)

回到_object_set_associative_reference函数,紧接着会判断是否能在AssociationHashMap中找到被关联对象所对应的ObjectAssociationMap表。

  1. 如果找不到就什么都不用做了。

  2. 如果找到了对应的ObjectAssociationMap表,就会调用find函数在ObjectAssociationMap表中查找对应的keyvalue(也就是ObjcAssociation)是否存在?

由于通过伪装对象查找ObjectAssociationMap,跟通过key查找ObjcAssociation对象都是DenseMapfind函数。

  • 结论:
    • ①. 如果ObjcAssociation不存在,也什么都不用做。

    • ②. 如果ObjcAssociation存在,就会将ObjectAssociationMap表中key所对应的value中的policy以及value设置为相应策略以及nil值,然后调用erase函数擦除ObjectAssociationMap中这对键值。

      • 再判断ObjectAssociationMap表中Buckets是否为空,如果为空,就会调用erase函数擦除AssociationHashMap中被关联对象与ObjectAssociationMap这对键值。

associations.erase

这个函数是擦除key对应的ObjcAssociation,以及diguised对应的ObjectAssociationMap对应的方法.

  • 擦除的c++源码:
class DenseMapBase {
...
 void erase(iterator I) {
    BucketT *TheBucket = &*I;
    TheBucket->getSecond().~ValueT();
    TheBucket->getFirst() = getTombstoneKey();
    decrementNumEntries();
    incrementNumTombstones();
    compact();
  }
...  
}

关联对象的取值

想要获取关联对象的值,可以通过调用objc_getAssociatedObject函数来获取。而在objc_getAssociatedObject函数中是通过_object_get_associative_reference取值的

  • 如下所示:
id
objc_getAssociatedObject(id object, const void *key)
{
    return _object_get_associative_reference(object, key);
}

id 
_object_get_associative_reference(id object, const void *key)
{
    ObjcAssociation association{};

    {
        AssociationsManager manager;
        AssociationsHashMap &associations(manager.get());
        // 传入objc_object对象,在find函数底层会转化为DisguiedPtr
        AssociationsHashMap::iterator i = associations.find((objc_object *)object);
        if (i != associations.end()) {
            ObjectAssociationMap &refs = i->second;
            ObjectAssociationMap::iterator j = refs.find(key);
            if (j != refs.end()) {
                association = j->second;
                association.retainReturnedValue();
            }
        }
    }

    return association.autoreleaseReturnedValue();
}
  • 结论:

    可以发现关联对象的取值流程关联对象的删除流程是极其相似的,

    • 如果association不为空,由于关联策略是OBJC_ASSOCIATION_SETTER_RETAIN类型,只会在设置关联对象时,才对这个关联对象的引用计数值加1。但是取值时,并不会对关联对象加1

    • 最后返回这个关联对象值,这就是关联对象取值的整个过程了。

      • 打印关联对象:
(lldb) p association
(objc::ObjcAssociation) $4 = {
  _policy = 1
  _value = 0x0000600000200040
}
(lldb) x/4gx $0
0x600000200040: 0x021d800100008765 0x00000001000040c8
0x600000200050: 0x0000000000000001 0x0000000000000000
(lldb) 

retainReturnedValue

这个函数是在c++ObjcAssociation里的内联函数,其实就是判断设置策略是否为OBJC_ASSOCIATION_GETTER_RETAIN,要不要通过objc_retain进行引用计数器+1

  • 其代码如下图所示: 截屏2023-03-13 下午9.12.07.png

  • 结论:

    由于设置关联对象策略是OBJC_ASSOCIATION_RETAIN_NONATOMIC,所以判断条件不成立,不会进入objc_retain

autoreleaseReturnedValue

这个函数也是在c++ObjcAssociation里的内联函数,其实就是判断设置策略是否为OBJC_ASSOCIATION_GETTER_AUTORELEASE,要不要通过objc_autorelease进行自动释放来延时释放?

  • 其代码如下图所示: 截屏2023-03-13 下午9.24.34.png

  • 结论:

    由于设置关联对象策略是OBJC_ASSOCIATION_RETAIN_NONATOMIC,刚好对应了所以判断条件不成立,不会进入objc_autorelease,而是直接返回_valueCJPet对象它的引用计数器刚好为2

注意点:

在里面C++objc_AssociationPolicy中的关联对象的策略只有5种,跟ObjcAssociation7策略是有所不同的。关键是这7种策略会区分settergetter情况。

删除所有关联对象

但目前为止,我们已经知道了单个关联对象的设值、删除、以及取值流程,那么如果一个被关联对象调用dealloc方法释放的时候,其所有的关联对象又是如何处理的呢?其实在查看objc4-866.9关联源码的时候我们已经注意到了一个函数,就是objc_removeAssociatedObjects

  • 其代码如下:
void objc_removeAssociatedObjects(id object) 
{
    if (object && object->hasAssociatedObjects()) {
  
        _object_remove_associations(object, /*deallocating*/false);
    }
}

_object_remove_assocations

在这个函数中,首先会判断被关联对象是否存在并且被关联对象是否有关联对象,如果为真,就会调用_object_remove_assocations函数。

在里面加入自己类CJPerson的判断条件,就可以断点调试了。

  • 代码如下:
// 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.
void
_object_remove_associations(id object, bool deallocating)
{
    ObjectAssociationMap refs{};
    //------ 测试代码为了能在断点时暂停,源码里不存在
    const char *personName = "CJPerson";
    if (strcmp(object_getClassName(object), personName) == 0) {
        printf("销毁关联对象");
    }

    {
        AssociationsManager manager;
        AssociationsHashMap &associations(manager.get());
        AssociationsHashMap::iterator i = associations.find((objc_object *)object);
        if (i != associations.end()) {
            refs.swap(i->second);

            // If we are not deallocating, then SYSTEM_OBJECT associations are preserved.
            bool didReInsert = false;
            if (!deallocating) {
                for (auto &ref: refs) {
                    if (ref.second.policy() & OBJC_ASSOCIATION_SYSTEM_OBJECT) {
                        i->second.insert(ref);
                        didReInsert = true;
                    }
                }
            }
            if (!didReInsert)
                associations.erase(i);
        }
    }

    // Associations to be released after the normal ones.
    SmallVector<ObjcAssociation *, 4> laterRefs;

    // release everything (outside of the lock).
    for (auto &i: refs) {
        if (i.second.policy() & OBJC_ASSOCIATION_SYSTEM_OBJECT) {
            // If we are not deallocating, then RELEASE_LATER associations don't get released.
            if (deallocating)
                laterRefs.append(&i.second);
        } else {
            i.second.releaseHeldValue();
        }
    }
    for (auto *later: laterRefs) {
        later->releaseHeldValue();
    }
}
  • 总结:

    在这个函数中,首先创建一个ObjectAssociationMap类型的临时变量refs,其成员变量初始化为对应的空值。

    • ①. 然后会根据manager变量的get()函数获取到AssociationsHashMap这张全局哈希表(存放的是所有被关联对象以及其对应的ObjectAssociationMap表),

    • ②. 在这张表中查找传入的被关联对象object是否存在对应的ObjectAssociationMap表,如果找到了,就将这个被关联对象objectObjectAssociationMap表中的数据域refs中的数据进行交换,

    • ③. 初始化一个bool类型的变量didReInsert(用来判断是不是重新插入),初始值为false,判断传入的参数deallocating(释放分配空间)的值是否为false,如果不为false,那么就是重新插入值,

    • 然后会遍历refsObjectAssociationMap表,存放的是设置关联对象时传入的key以及对应的ObjcAssociation(包装了关联策略以及关联对象)变量)中Buckets中每个Bucket,调用Bucket对中secondpolicy()函数获取此时这个Bucket中关联对象的关联策略,&OBJC_ASSOCIATION_SYSTEM_OBJECT这个枚举值,

    • 如果值为真,也就是表示是此关联对象为系统对象,就将这个关联对象重新插入到被关联对象在AssociationsHashMap表中所对应的ObjectAssociationMap表中,然后将didReInsert赋值为true

    • 如果遍历完整个ObjcAssociationMap表都没有需要重新插入的系统对象,就调用erase函数将AssociationsHashMap表中此被关联对象对应的关联对象信息抹除,

    • 然后再次遍历refs这个表中所有的key以及对应的关联对象信息,调用releaseHeldValue函数将所有非系统对象的关联对象的引用计数值减1,然后定义一个SmallVector类型的laterRefs存储所有系统的关联对象,最后将laterRefs中所有的关联对象引用计数值减1

_object_remove_assocations的调用情况

但是什么情况下会调用_object_remove_assocations这个函数呢? 通过lldb调试,bt指令打印整个函数调用栈。

  • lldb打印:
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 22.1
    frame #0: 0x00000001006f081c libobjc.A.dylib`_object_remove_associations(object=0x0000600000209dc0, deallocating=true) at objc-references.mm:233:9
    frame #1: 0x00000001006ba87e libobjc.A.dylib`objc_destructInstance(obj=0x0000600000209dc0) at objc-runtime-new.mm:8600:20
    frame #2: 0x00000001006baafd libobjc.A.dylib`object_dispose(obj=0x0000600000209dc0) at objc-runtime-new.mm:8618:5
    frame #3: 0x0000000100704f08 libobjc.A.dylib`objc_object::rootDealloc(this=0x0000600000209dc0) at objc-object.h:581:9
    frame #4: 0x0000000100704dc0 libobjc.A.dylib`_objc_rootDealloc(obj=0x0000600000209dc0) at NSObject.mm:2113:10
    frame #5: 0x0000000100708689 libobjc.A.dylib`-[NSObject dealloc](self=0x0000600000209dc0, _cmd="dealloc") at NSObject.mm:2706:5
    frame #6: 0x00000001006ff335 libobjc.A.dylib`objc_object::performDealloc(this=0x0000600000209dc0) at NSObject.mm:1538:9
    frame #7: 0x00000001006fc158 libobjc.A.dylib`_objc_release [inlined] objc_object::rootRelease(this=0x0000600000209dc0, performDealloc=true, variant=FastOrMsgSend) at objc-object.h:898:15
    frame #8: 0x00000001006fb7f0 libobjc.A.dylib`_objc_release [inlined] objc_object::release(this=0x0000600000209dc0) at objc-object.h:714:5
    frame #9: 0x00000001006fb792 libobjc.A.dylib`_objc_release(obj=0x0000600000209dc0) at NSObject.mm:1888:17
  * frame #10: 0x00000001006fb6fb libobjc.A.dylib`objc_storeStrong(location=0x00007ff7bfeff1e0, obj=0x0000000000000000) at NSObject.mm:281:5
    frame #11: 0x00000001000034fe KCObjcBuild`main(argc=1, argv=0x00007ff7bfeff4d8) at main.m:280:5 [opt]
    frame #12: 0x00007ff80569a310 dyld`start + 2432
  • 结论:

    通过打印结果可发现删除关联对象的流程是:

    • [NSObject dealloc] -> _objc_rootDealloc -> objc_object::rootDealloc() -> object_dispose -> objc_destructInstance -> _object_remove_associations
  1. 我们来反向查找一下,全局搜索_object_remove_assocations关键字,也可以发现在如下图所示的函数中被调用: image.png

  2. objc_destructInstance函数又在object_dispose函数中被调用,如下图所示: image.png

  3. object_dispose函数在objc_object::rootDealloc函数中被调用,如下图所示: image.png

  4. objc_object::rootDealloc函数在_objc_rootDealloc函数中被调用,如下图所示: image.png

  5. 而最后,_objc_rootDealloc函数在dealloc方法中被调用,当对象的引用计数为0的时候系统会对其dealloc方法进行调用,这就是删除对象的所有关联对象的调用流程了。

三、总结

经过以上探讨,我们可知关于一个对象的关联对象的存储在底层中实际上是一个两层哈希表结构。

  • 如下图所示: 截屏2023-03-14 下午12.39.16.png