Objective-C 的 RunTime(三):Category 底层原理

Category 简介

  • Category 是 Objective-C 2.0 之后添加的语言特性,其主要作用是为已经存在的类添加方法(无论工程中是否有该类的源码)。Category 可以做到在既不子类化,也不侵入一个类的源码的情况下,为原有的类添加新的方法,从而实现扩展一个类或者分离一个类的目的

  • 程序在启动时,RunTime 会把 Category 中的内容附加到宿主类中

  • 本文主要用于介绍 Category 的底层原理,关于 Category 的使用方式与注意事项,请参考:
    Objective-C 的 Category 与 Extension

Category 的本质

  • Category 结构体简介

    Category(分类)被定义为指向 struct category_t 的指针,其数据结构如下:

    // 一个不透明的类型,用于表示 Objective-C 中的一个分类
    typedef struct category_t *Category;
    
    // 表示一个类的一个分类
    struct category_t {
        const char *name;  							// 宿主类名称
        classref_t cls;  							// 宿主类对象,在运行时阶段通过(宿主类名称)找到对应的(宿主类对象)
        struct method_list_t *instanceMethods;  	// 分类的对象方法列表
        struct method_list_t *classMethods;  		// 分类的类方法列表
        struct protocol_list_t *protocols;  		// 分类所遵守的协议列表(注意:不是分类所定义的协议列表)
        struct property_list_t *instanceProperties;	// 分类的属性列表
    };
    

    struct category_t 的定义中可以看出
    Category 可以为类添加:对象方法、类方法、协议、属性
    Category 不能为类添加:成员变量

  • 编译 Category 的 C++ 源码

    想要了解 Category 的本质,需要借助 Category 的 C++ 源码

    ① 新建一个继承自 NSObjectPerson

    ② 新建一个 UsingToolProtocol 协议

    ③ 为 Person 类新建一个 Category:Addition ,在其中添加:协议、属性、对象方法、类方法

    ④ 将 Person+Addition.m 编译为 Person+Addition.cpp

     ~/Desktop/Training/CategoryDemo/CategoryDemo > xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc Person+Addition.m -o Person+Addition.cpp
    

    ⑤ 当我们得到 Person+Addition.cpp 文件之后,就会神奇地发现:这是一个 1.7M 大小,拥有近 3.5W 行代码的庞大文件。不过不用慌,与 Category 相关的 C++ 源码在文件的最底部。删除其他无关代码,只保留 Category 有关的代码,大概就会剩下 200 多行代码。下面我们根据 Category 结构体的不同成员,分模块来讲解一下

  • C++ 源码中『Category 结构体』

    // C++ 源码中关于分类结构体的定义 与 RunTime 源码中关于分类结构体的定义,在本质上是一一对应的,可以看做是同一个结构体
    struct _category_t {
    	const char *name;
    	struct _class_t *cls;
    	const struct _method_list_t *instance_methods;
    	const struct _method_list_t *class_methods;
    	const struct _protocol_list_t *protocols;
    	const struct _prop_list_t *properties;
    };
    
    // 为 Person 类的 Addition 分类结构体赋值
    static struct _category_t _OBJC_$_CATEGORY_Person_$_Addition __attribute__ ((used, section ("__DATA,__objc_const"))) = 
    {
    	"Person",
    	0, // &OBJC_CLASS_$_Person,
    	(const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_Person_$_Addition,
    	(const struct _method_list_t *)&_OBJC_$_CATEGORY_CLASS_METHODS_Person_$_Addition,
    	(const struct _protocol_list_t *)&_OBJC_CATEGORY_PROTOCOLS_$_Person_$_Addition,
    	(const struct _prop_list_t *)&_OBJC_$_PROP_LIST_Person_$_Addition,
    };
    
    // Person 类的分类结构体数组,存放着与 Person 类相关的分类,如果 Person 类有多个分类,则分类结构体数组中存放对应数目的分类结构体
    static struct _category_t *L_OBJC_LABEL_CATEGORY_$ [1] __attribute__((used, section ("__DATA, __objc_catlist,regular,no_dead_strip")))= {
    	&_OBJC_$_CATEGORY_Person_$_Addition,
    };
    
  • C++ 源码中 Category 的『对象方法列表结构体』

    // Addition 分类中定义的对象方法 -instanceMethodEat 的实现
    static void _I_Person_Addition_instanceMethodEat(Person * self, SEL _cmd) {
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_92_vshp0dxd0cg90_kzw6k3mlfm0000gn_T_Person_Addition_d8df92_mi_0);
    }
    
    // Addition 分类中定义的对象方法 -instanceMethodSleep 的实现
    static void _I_Person_Addition_instanceMethodSleep(Person * self, SEL _cmd) {
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_92_vshp0dxd0cg90_kzw6k3mlfm0000gn_T_Person_Addition_d8df92_mi_1);
    }
    
    // UsingToolProtocol 协议中定义的对象方法 -usingCompputer 的实现
    static NSString * _Nonnull _I_Person_Addition_usingCompputer(Person * self, SEL _cmd) {
        return (NSString *)&__NSConstantStringImpl__var_folders_92_vshp0dxd0cg90_kzw6k3mlfm0000gn_T_Person_Addition_d8df92_mi_4;
    }
    
    // NSCopying 协议中定义的对象方法 -copyWithZone: 的实现
    static id _Nonnull _I_Person_Addition_copyWithZone_(Person * self, SEL _cmd, NSZone * _Nullable zone) {
        return self;
    }
    
    // 方法列表结构体的定义 + 分类对象方法列表结构体的赋值
    // 只要是 Addition 分类中实现了的对象方法(包括所遵守的协议中的对象方法),都会添加到对象方法列表结构体 _OBJC_$_CATEGORY_INSTANCE_METHODS_Person_$_Addition 中来。如果只是在 Addition 分类中定义,而没有实现,则不会添加
    static struct /*_method_list_t*/ {
    	unsigned int entsize;  // sizeof(struct _objc_method)
    	unsigned int method_count;
    	struct _objc_method method_list[4];
    } _OBJC_$_CATEGORY_INSTANCE_METHODS_Person_$_Addition __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    	sizeof(_objc_method),
    	4,
    	{{(struct objc_selector *)"instanceMethodEat", "v16@0:8", (void *)_I_Person_Addition_instanceMethodEat},
    	{(struct objc_selector *)"instanceMethodSleep", "v16@0:8", (void *)_I_Person_Addition_instanceMethodSleep},
    	{(struct objc_selector *)"usingCompputer", "@16@0:8", (void *)_I_Person_Addition_usingCompputer},
    	{(struct objc_selector *)"copyWithZone:", "@24@0:8^{_NSZone=}16", (void *)_I_Person_Addition_copyWithZone_}}
    };
    
  • C++ 源码中 Category 的『类方法列表结构体』

    // Addition 分类中定义的类方法 +classMethodRun 的实现
    static void _C_Person_Addition_classMethodRun(Class self, SEL _cmd) {
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_92_vshp0dxd0cg90_kzw6k3mlfm0000gn_T_Person_Addition_d8df92_mi_2);
    }
    
    // Addition 分类中定义的类方法 +classMethodWalk 的实现
    static void _C_Person_Addition_classMethodWalk(Class self, SEL _cmd) {
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_92_vshp0dxd0cg90_kzw6k3mlfm0000gn_T_Person_Addition_d8df92_mi_3);
    }
    
    // UsingToolProtocol 协议中定义的类方法 +usingBike 的实现
    static NSString * _Nonnull _C_Person_Addition_usingBike(Class self, SEL _cmd) {
        return (NSString *)&__NSConstantStringImpl__var_folders_92_vshp0dxd0cg90_kzw6k3mlfm0000gn_T_Person_Addition_d8df92_mi_5;
    }
    
    // UsingToolProtocol 协议中定义的类方法 +allocWithZone: 的实现
    static instancetype _C_Person_Addition_allocWithZone_(Class self, SEL _cmd, struct _NSZone *zone) {
        return ((id (*)(id, SEL))(void *)objc_msgSend)((id)((id (*)(id, SEL))(void *)objc_msgSend)((id)self, sel_registerName("alloc")), sel_registerName("init"));
    }
    
    // 方法列表结构体的定义 + 分类类方法列表结构体的赋值
    // 只要是 Addition 分类中实现了的类方法(包括所遵守的协议中的类方法),都会添加到类方法列表结构体 _OBJC_$_CATEGORY_CLASS_METHODS_Person_$_Addition 中来。如果只是在 Addition 分类中定义,而没有实现,则不会添加
    static struct /*_method_list_t*/ {
    	unsigned int entsize;  // sizeof(struct _objc_method)
    	unsigned int method_count;
    	struct _objc_method method_list[4];
    } _OBJC_$_CATEGORY_CLASS_METHODS_Person_$_Addition __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    	sizeof(_objc_method),
    	4,
    	{{(struct objc_selector *)"classMethodRun", "v16@0:8", (void *)_C_Person_Addition_classMethodRun},
    	{(struct objc_selector *)"classMethodWalk", "v16@0:8", (void *)_C_Person_Addition_classMethodWalk},
    	{(struct objc_selector *)"usingBike", "@16@0:8", (void *)_C_Person_Addition_usingBike},
    	{(struct objc_selector *)"allocWithZone:", "@24@0:8^{_NSZone=}16", (void *)_C_Person_Addition_allocWithZone_}}
    };
    
  • C++ 源码中 Category 的『协议列表结构体』

    // 方法列表结构体的定义 + UsingToolProtocol 对象方法列表结构体的赋值
    static struct /*_method_list_t*/ {
    	unsigned int entsize;  // sizeof(struct _objc_method)
    	unsigned int method_count;
    	struct _objc_method method_list[1];
    } _OBJC_PROTOCOL_INSTANCE_METHODS_UsingToolProtocol __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    	sizeof(_objc_method),
    	1,
    	{{(struct objc_selector *)"usingCompputer", "@16@0:8", 0}}
    };
    
    // 方法列表结构体的定义 + UsingToolProtocol 类方法列表结构体的赋值
    static struct /*_method_list_t*/ {
    	unsigned int entsize;  // sizeof(struct _objc_method)
    	unsigned int method_count;
    	struct _objc_method method_list[1];
    } _OBJC_PROTOCOL_CLASS_METHODS_UsingToolProtocol __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    	sizeof(_objc_method),
    	1,
    	{{(struct objc_selector *)"usingBike", "@16@0:8", 0}}
    };
    
    // C++ 源码中关于协议结构体的定义 与 RunTime 源码中关于协议结构体的定义,在本质上是一一对应的,可以看做是同一个结构体
    struct _protocol_t {
    	void * isa;  // NULL
    	const char *protocol_name;
    	const struct _protocol_list_t * protocol_list; // super protocols
    	const struct method_list_t *instance_methods;
    	const struct method_list_t *class_methods;
    	const struct method_list_t *optionalInstanceMethods;
    	const struct method_list_t *optionalClassMethods;
    	const struct _prop_list_t * properties;
    	const unsigned int size;  // sizeof(struct _protocol_t)
    	const unsigned int flags;  // = 0
    	const char ** extendedMethodTypes;
    };
    
    // UsingToolProtocol 协议结构体的赋值
    struct _protocol_t _OBJC_PROTOCOL_UsingToolProtocol __attribute__ ((used)) = {
    	0,
    	"UsingToolProtocol",
    	(const struct _protocol_list_t *)&_OBJC_PROTOCOL_REFS_UsingToolProtocol,
    	(const struct method_list_t *)&_OBJC_PROTOCOL_INSTANCE_METHODS_UsingToolProtocol,
    	(const struct method_list_t *)&_OBJC_PROTOCOL_CLASS_METHODS_UsingToolProtocol,
    	0,
    	0,
    	0,
    	sizeof(_protocol_t),
    	0,
    	(const char **)&_OBJC_PROTOCOL_METHOD_TYPES_UsingToolProtocol
    };
    struct _protocol_t *_OBJC_LABEL_PROTOCOL_$_UsingToolProtocol = &_OBJC_PROTOCOL_UsingToolProtocol;
    
    // NSCopying 协议结构体的赋值
    struct _protocol_t _OBJC_PROTOCOL_NSCopying __attribute__ ((used)) = {
    	0,
    	"NSCopying",
    	0,
    	(const struct method_list_t *)&_OBJC_PROTOCOL_INSTANCE_METHODS_NSCopying,
    	0,
    	0,
    	0,
    	0,
    	sizeof(_protocol_t),
    	0,
    	(const char **)&_OBJC_PROTOCOL_METHOD_TYPES_NSCopying
    };
    struct _protocol_t *_OBJC_LABEL_PROTOCOL_$_NSCopying = &_OBJC_PROTOCOL_NSCopying;
    
    // 协议列表结构体的定义 + 分类协议列表结构体的赋值
    // 只要是 Addition 分类中所遵守的协议(注意:不是 Addition 分类中所定义的协议),都会添加到协议列表结构体 _OBJC_CATEGORY_PROTOCOLS_$_Person_$_Addition 中来。如果只是在 Addition 分类中定义,而没有遵守,则不会添加
    static struct /*_protocol_list_t*/ {
    	long protocol_count;  // Note, this is 32/64 bit
    	struct _protocol_t *super_protocols[2];
    } _OBJC_CATEGORY_PROTOCOLS_$_Person_$_Addition __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    	2,
    	&_OBJC_PROTOCOL_UsingToolProtocol,
    	&_OBJC_PROTOCOL_NSCopying
    };
    
  • C++ 源码中 Category 的『属性列表结构体』

    // C++ 源码中关于属性结构体的定义 与 RunTime 源码中关于属性结构体的定义,在本质上是一一对应的,可以看做是同一个结构体
    struct _prop_t {
    	const char *name;
    	const char *attributes;
    };
    
    // 属性列表结构体的定义 + 分类属性列表结构体的赋值
    static struct /*_prop_list_t*/ {
    	unsigned int entsize;  // sizeof(struct _prop_t)
    	unsigned int count_of_properties;
    	struct _prop_t prop_list[2];
    } _OBJC_$_PROP_LIST_Person_$_Addition __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    	sizeof(_prop_t),
    	2,
    	{{"name","T@\"NSString\",&,D,N"},
    	{"age","Tq,D,N"}}
    };
    
  • C++ 源码中 Category 的『其他细节』

    ① 因为 PersonProtocol 协议在 Person+Addition 分类中只是被定义,没有被遵守,所以 PersonProtocol 协议在编译后的 C++ 源码中,变成了一块注释。这也从侧面说明了:分类中的协议列表,存储的是分类所遵守的协议,而不是分类所定义的协议

    // @protocol PersonProtocol <NSObject>
    
    // - (void)personProtocolInstanceMethod00;
    // - (void)personProtocolInstanceMethod01;
    // + (void)personProtocolClassMethod00;
    // + (void)personProtocolClassMethod01;
    
    /* @end */
    

    ② 在编译后的 C++ 源码中,没有找到分类关于成员变量结构体的定义,也没有找到分类关于成员变量列表结构体的定义,更没有找到分类关于属性 gettersetter 的实现。这也从侧面说明了:分类中不能添加成员变量;分类中添加的属性,只会生成 gettersetter 的声明,不会生成 gettersetter 的实现

Category 的加载流程

  • (dyld 加载和链接动态库的流程)与(runtime 库的加载)

    MachO && dyld(五)中,我们介绍了 dyld 加载和链接动态库的流程,整个流程可细分为 9 步 :
    ① 设置运行环境
    ② 加载系统共享缓存
    ③ 实例化主程序
    ④ 加载插入的动态库
    ⑤ 链接主程序
    ⑥ 链接插入的动态库
    ⑦ 执行弱符号绑定
    ⑧ 执行初始化方法
    ⑨ 查找 App 入口点并返回

    这里重点关注:⑧ 执行初始化方法,在这一步中:

    dyldImageLoaderMachO::doModInitFunctions 函数会去初始化 libSystem.dylib 库,并调用 libSystem.dylib 库的 libSystem_initializer 函数

    libSystem_initializer 函数会去初始化 libdispatch.dylib 库,并调用 libdispatch.dylib 库的 libdispatch_init 函数。接着 libdispatch_init 函数又会去调用 _os_object_init 函数

    最后 libdispatch.dylib 库的 _os_object_init 函数会去初始化 libobjc.dylib 库,并调用 libobjc.dylib 库的 _objc_init 函数

    整个函数调用栈如下图所示:
    _objc_init 函数调用栈
    其中,_objc_init 为 RunTime 的入口函数,用于进行一些初始化操作。其源码如下:

    // path: objc4-756.2/runtime/objc-os.mm
    // Runtime 的入口函数, 用于进行引导程序的初始化, 在库初始化之前由 libSystem 调用
    void _objc_init(void)
    {
        static bool initialized = false;
        if (initialized) return;
        initialized = true;
        
        // 修复程序会延迟初始化直到找到一个 objc-using 的镜像为止 ?
        environ_init();     // 初始化运行时的环境变量. 主要是读取影响 Runtim 的一些环境变量. 如果需要, 也可以输出环境变量的帮助提示 (在终端上直接输入命令 exprot OBJC_HELP=1)
        tls_init();         // 初始化 TLS (安全传输层协议). 这里执行的是关于线程 key 的绑定, 比如每条线程 数据的析构函数
        static_init();      // 初始化 C++ 的静态构造函数. 因为在 dyld 调用静态构造函数之前, libc 会调用 _objc_init(), 所以我们必须自己执行 C++ 静态构造函数. 这里只会初始化系统内置的 C++ 构造函数
        lock_init();        // 初始化锁. objc 的锁完全是采用 C++ 的锁的那一套逻辑
        exception_init();   // 初始化 libobjc 的异常处理系统
    
        // 向 dyld 注册镜像相关的回调通知(map_images:映射镜像, load_images:加载镜像/初始化镜像, unmap_image:卸载镜像)
        _dyld_objc_notify_register(&map_images, load_images, unmap_image);
    }
    

    可以看到,在 RunTime 执行完所有初始化操作之后,调用了 _dyld_objc_notify_register 函数。_dyld_objc_notify_register 是 dyld 向 RunTime 提供的接口函数,RunTime 通过此函数向 dyld 注册回调。当 dyld(映射镜像、初始化镜像、卸载镜像)时,会通过 RunTime 注册的这些回调通知 RunTime 进行相应的处理

  • 探索 RunTime 加载与处理 Category 的入口点

    在 dyld 映射镜像时,会调用 RunTime 的 map_images 函数,其函数调用栈如下图所示:
    map_images 函数调用栈
    map_images 函数用于映射镜像前的加锁操作,其源码如下所示:
    map_images 函数
    map_images_nolock 函数用于镜像信息的提取与统计,其源码如下所示:
    map_images_nolock 函数
    _read_images 函数用于读取镜像,进行(类、方法编号、协议、分类)等的处理,并输出统计信息。RunTime 加载与处理 Category 的流程由本函数的第 7 步开始,其源码如下所示:
    _read_images 函数
    可以看到,RunTime 加载与处理 Category 主要用到了 2 个函数:

    1. addUnattachedCategoryForClass 函数,用于提取宿主类未附加的分类的列表,建立未附加的分类与宿主类的映射关系
    2. remethodizeClass 函数,用于重建宿主类的(方法列表、属性列表、协议列表)

    通过这 2 个函数达到了 2 个目的:

    1. 把 Category(分类) 的对象方法、协议、属性附加到宿主类上
    2. 把 Category(分类) 的类方法、协议附加到宿主类的 meta-class 上
  • RunTime 加载与处理 Category 的流程

    ① 调用 addUnattachedCategoryForClass 函数,从全局未附加的分类的列表中(cats),获取宿主类未附加的分类的列表(list),并建立未附加的分类(cat)与宿主类的映射关系:
    addUnattachedCategoryForClass
    ② 调用 remethodizeClass 函数,重建宿主类的(方法列表数组、属性列表数组、协议列表数组):
    remethodizeClass
    可以看到,remethodizeClass 函数其实就做了一件事:调用 attachCategories 函数,attachCategories 函数是 RunTime 用于加载与处理 Category 的核心函数:
    attachCategories
    attachCategories 函数中,用于整合宿主类(方法列表数组、属性列表数组、协议列表数组)与 Category(方法列表数组、属性列表数组、协议列表数组)的函数为 attachLists
    attachLists
    注意:

    addUnattachedCategoryForClass 函数主要是向一个全局哈希表 category_map 里面添加宿主类对应的未附加的分类,一个宿主类所有的未附加的分类都在其 list
    unattachedCategoriesForClass 函数主要是从全局哈希表 category_map 里面取出一个宿主类所有的未附加的分类列表 list
    category_map

Association Object(关联对象)底层原理

  • Association Object(关联对象)的简单使用

    在 Category 中使用 @property 添加属性时,编译器只会生成属性 getter setter 方法的声明,并不会生成 _成员变量,更不会生成属性 getter setter 方法的实现(这将导致我们无法访问成员变量,也无法自己手动实现成员变量的存取方法)。那么在 Category 中添加一个不能访问的属性有什么意义呢?

    实际上,可以使用 RunTime 的 Association Object(关联对象)为 Category 已有的属性设置一个关联值,并添加 getter setter 方法的实现

    // Person+SportLover.h
    #import "Person.h"
    
    @interface Person (SportLover)
    
    @property (nonatomic, strong) NSString* sportName;
    
    @end
    ------------------------------------------------------------------------------------------------
    // Person+SportLover.m
    #import "Person+SportLover.h"
    #import <objc/runtime.h>
    
    // 关联对象中属性的 key 值,需要确保全局唯一性
    static NSString* kSportNameKey = @"Person+SportLover.sportName";
    
    @implementation Person (SportLover)
    
    // 通过 RunTime 的关联对象,为分类的属性实现 getter
    -(NSString *)sportName {
        return objc_getAssociatedObject(self, &kSportNameKey);
    }
    
    // 通过 RunTime 的关联对象,为分类的属性实现 setter
    -(void)setSportName:(NSString *)sportName {
        objc_setAssociatedObject(self, &kSportNameKey, sportName, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }
    
    @end
    

    注意:
    虽然可以通过 Association Object(关联对象)为 Category 的属性动态地添加 getter setter 方法的实现
    但是编译器自始至终都没有为 Category 的属性生成带下划线的成员变量
    所以在代码中,如果使用 _成员变量 的方式调用 Category 属性对应的成员变量,程序还是会报错

    RunTime 中用于 添加、获取、移除 指定对象上的关联对象的函数,如下所示:

    // 用给定的 key 与关联策略为指定的对象设置关联值
    // param0.object 	源对象
    // param1.key		用来标记关联值的 key
    // param2.value		关联值
    // param3.policy	关联策略
    void
    objc_setAssociatedObject(id _Nonnull object, const void * _Nonnull key,
                             id _Nullable value, objc_AssociationPolicy policy)
                             
    // 用给定的 key 获取指定对象的关联值
    // param0.object 	源对象
    // param1.key		用来标记关联值的 key
    // return			关联值
    id _Nullable
    objc_getAssociatedObject(id _Nonnull object, const void * _Nonnull key)
    
    // 移除指定对象上的所有关联值
    // param0.object 	源对象
    void
    objc_removeAssociatedObjects(id _Nonnull object)
    
    // 关联策略
    typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) {
        OBJC_ASSOCIATION_ASSIGN = 0,           	//关联对象的属性是弱引用
        OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1,	//关联对象的属性是强引用并且关联对象不使用原子性                   
        OBJC_ASSOCIATION_COPY_NONATOMIC = 3,	//关联对象的属性是 copy 并且关联对象不使用原子性   
        OBJC_ASSOCIATION_RETAIN = 01401, 		//关联对象的属性是强引用并且关联对象使用原子性     
        OBJC_ASSOCIATION_COPY = 01403  			//关联对象的属性是 copy 并且关联对象使用原子性        
    };
    
    // 注意:
    objc_removeAssociatedObjects(...) 函数的作用是移除指定对象上的所有关联值
    如果要移除指定对象上的某个关联值,可以使用 objc_setAssociatedObject(object, &key, nil, policy),即关联值 value 传 nil
    
    关联对象由 AssociationsManager 统一管理
    所有对象的关联内容统一放在一个容器:全局哈希表 AssociationsHashMap 中
    

    Association Object(关联对象)key 的常见用法,如下所示:

    // 1.使用静态常量指针
    static const void * kPropertykey = &kPropertykey;
    objc_setAssociatedObject(object, kPropertykey, value, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    objc_getAssociatedObject(object, kPropertykey);
    
    // 2.使用静态常量的地址(以下五种定义静态常量的方式均可使用)
    //static const char kPropertyKey;
    //static const char * kPropertyKey;
    //static const char * kPropertyKey = "ClassName+CategoryName.propertyName";
    //static const NSString * kPropertyKey;
    static const NSString * kPropertyKey = @"ClassName+CategoryName.propertyName";
    objc_setAssociatedObject(object, &kPropertykey, value, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    objc_getAssociatedObject(object, &kPropertykey);
    
    // 3.使用 getter 方法的 SEL 作为 key(可读性高,有智能提示)
    objc_setAssociatedObject(object, @selector(getter), value, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    objc_getAssociatedObject(object, @selector(getter));
    
  • Association Object(关联对象)的底层数据结构

    ① AssociationsManager

    // 一个宿主对象的关联对象,并非存储在该宿主对象本身的内存中。而是所有宿主对象的关联对象统一存储在一个由 AssociationsManager 管理和维护的全局的容器中
    // AssociationsManager 管理和维护了一个全局静态的 AssociationsHashMap,用于存储所有宿主对象的关联对象,并且使用锁机制保证了线程安全
    class AssociationsManager {
        // associative references: object pointer -> PtrPtrHashMap.
        static AssociationsHashMap *_map;
    public:
        AssociationsManager()   { AssociationsManagerLock.lock(); }
        ~AssociationsManager()  { AssociationsManagerLock.unlock(); }
        
        AssociationsHashMap &associations() {
            if (_map == NULL)
                _map = new AssociationsHashMap();
            return *_map;
        }
    };
    

    ② AssociationsHashMap

    // 哈希表,用于存储 disguised_ptr_t 和 ObjectAssociationMap 键值对
    // disguised_ptr_t 是根据宿主对象 object 生成的,但不存在引用关系:
    // disguised_ptr_t disguised_object = DISGUISE(object);
    class AssociationsHashMap : public unordered_map<disguised_ptr_t, ObjectAssociationMap *, DisguisedPointerHash, DisguisedPointerEqual, AssociationsHashMapAllocator> {
    public:
        void *operator new(size_t n) { return ::malloc(n); }
        void operator delete(void *ptr) { ::free(ptr); }
    };
    

    ③ ObjectAssociationMap

    // 哈希表,用于存储 key 和 ObjcAssociation 键值对
    class ObjectAssociationMap : public std::map<void *, ObjcAssociation, ObjectPointerLess, ObjectAssociationMapAllocator> {
    public:
        void *operator new(size_t n) { return ::malloc(n); }
        void operator delete(void *ptr) { ::free(ptr); }
    };
    

    ④ ObjcAssociation

    // 存储着关联策略 policy 和关联值 value
    class ObjcAssociation {
        uintptr_t _policy;
        id _value;
    public:
        ObjcAssociation(uintptr_t policy, id value) : _policy(policy), _value(value) {}
        ObjcAssociation() : _policy(0), _value(nil) {}
    
        uintptr_t policy() const { return _policy; }
        id value() const { return _value; }
        
        bool hasValue() { return _value != nil; }
    };
    
  • AssociationsManager、AssociationsHashMap、ObjectAssociationMap、ObjcAssociation 之间的关系
    AssociationsManager、AssociationsHashMap、ObjectAssociationMap、ObjcAssociation 之间的关系

  • objc_setAssociatedObject 函数的底层实现
    objc_setAssociatedObject
    _object_set_associative_reference

  • objc_getAssociatedObject 函数的底层实现
    objc_getAssociatedObject
    _object_get_associative_reference

  • objc_removeAssociatedObjects 函数的底层实现
    objc_removeAssociatedObjects
    _object_remove_assocations

  • acquireValue 函数
    acquireValue

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值