一、类扩展

  1. 类扩展extension在咱们的开发进程中其实常常运用的,下图红框部分便是一个ViewController的类扩展。
  • 图:
    OC底层原理(十四)类扩展与关联对象
  1. 类扩展实际上是一个特别的分类,也称作匿名分类,创立的类扩展只有.h文件,没有.m文件。
  • 如下图所示:
    OC底层原理(十四)类扩展与关联对象
    OC底层原理(十四)类扩展与关联对象
  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"就在能够在类加载进程,断点调试了。
  • 如下图:
    OC底层原理(十四)类扩展与关联对象
  • 通过命令打印输出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持有此相关目标。

  • 如下所示:

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

  • 定论:

    acquireValue这个函数中会依据相关战略的不同,进行不同的存储假如相关战略是OBJC_ASSOCIATION_RETAIN或许OBJC_ASSOCIATION_RETAIN_NONATOMIC类型

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

      • 验证如下所示:

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

      • 成果:

        • 能够看到这个目标中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也是一个哈希表,是用来存储其相关目标信息,这两种类型的界说如下图所示:

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

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

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

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访问。
      OC底层原理(十四)类扩展与关联对象
    • ②. 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

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

  • 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的删去相关目标的代码如下图:
    OC底层原理(十四)类扩展与关联对象

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

  • 其代码如下图所示:

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

  • 定论:

    因为设置相关目标战略是OBJC_ASSOCIATION_RETAIN_NONATOMIC,所以判别条件不成立,不会进入objc_retain

autoreleaseReturnedValue

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

  • 其代码如下图所示:

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

  • 定论:

    因为设置相关目标战略是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关键字,也能够发现在如下图所示的函数中被调用:

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

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

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

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

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

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

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

  5. 而最终,_objc_rootDealloc函数在dealloc办法中被调用,当目标的引证计数为0的时分体系会对其dealloc办法进行调用,这便是删去目标的一切相关目标的调用流程了。

三、总结

通过以上探讨,咱们可知关于一个目标的相关目标的存储在底层中实际上是一个两层哈希表结构。

  • 如下图所示:
    OC底层原理(十四)类扩展与关联对象