• 【iOS】——分类、扩展和关联对象


    一、分类Category和扩展Extension

    1.分类Category(运行期)

    • 分类就是为已经存在的类添加新方法的一种新机制,当然也可以用作类各种逻辑代码的分区。
    • 它可以在运行时阶段动态的为已有的类添加新行为。
    • 分类还可以将 framework 私有方法公开化。

      例如,我们有一个类中有一个私有方法A,外界当然是调用不到的,但是我们可以通过创建该类新的分类,其中也定义一个方法A,只声明不实现,那么此时我们调用这个分类中的方法A就会调用该类中的私有方法A了,这就将私有方法公开化了。

    • 分类可以添加实例方法、类方法、协议和属性,但是不能声明实例变量。

    2.扩展Extension(编译期)

    • 扩展跟分类定义很像,但是它只有声明部分没有实现部分,并且扩展中定义的方法得需要我们在该类的实现部分去实现。
    • 扩展可以定义实例方法、类方法、协议、属性和实例变量,因为它会在编译期将其中定义的数据加到该类的各种数据列表中。
    • 不能为系统类添加扩展,因为扩展中定义的方法的实现是要在该类的实现部分去书写代码的,但是系统类不会对我们开放的,所以我们就无法实现扩展方法,所以我们也就不能为系统类添加扩展了。

    3.分类和扩展的区别

    • Extension 看起来很像一个匿名的 Category,但是 Extension 和有名字的category几乎完全是两个东西。 Extension 在编译期决议,它就是类的一部分,在编译期和头文件里的 @interface 以及实现文件里的 @implement 一起形成一个完整的类,它伴随类的产生而产生,亦随之一起消亡。Extension 一般用来隐藏类的私有信息,你必须有一个类的源码才能为一个类添加 Extension ,所以你无法为系统的类比如 NSString 添加 Extension。
    • 但是 Category 则完全不一样,它是在运行期决议的。就 Category 和 Extension 的区别来看,我们可以推导出一个明显的事实,Extension 可以添加实例变量,而 Category 是无法添加实例变量的(因为在运行时,对象的内存布局已经确定,如果添加实例变量就会破坏类的内部布局,这对编译型语言来说是灾难性的)。

    二、分类Category的实质

    1.分类的结构

    我们知道,所有的OC类和对象,在runtime层都是用struct结构体进行封装的,category也是如此,它被定义为category_t结构体,其数据结构如下:

    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; // 实例属性
        // Fields below this point are not always present on disk.
        struct property_list_t *_classProperties;   // 类属性
    
        method_list_t *methodsForMeta(bool isMeta) {
            if (isMeta) return classMethods;
            else return instanceMethods;
        }
    
        property_list_t *propertiesForMeta(bool isMeta, struct header_info *hi);
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    从它的结构中就能知道它不能添加实例变量,因为其中没有实例变量的定义。

    2.分类结构的源码解析

    我们先创建一个新的分类,其中定义一些方法和属性,方便我们解析。

    新建一个继承自NSObject的类的分类:

    @interface TestNSObject (TestCategoryNSObject)<TestCategoryProtocol>
    
    @property (nonatomic, copy) NSString *personName;
    
    - (void)TestCategoryMethod;
    + (void)TestCategoryClassMethod;
    
    @end
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    该分类遵守的协议:

    @protocol TestCategoryProtocol <NSObject>
    
    - (void)protocolMethod;
    
    + (void)protocolClassMethod;
    
    @end
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    并实现其中的方法:

    @implementation TestNSObject (TestCategoryNSObject)
    
    - (void)TestCategoryMethod {
        NSLog(@"TestCategoryMethod");
    }
    
    + (void)TestCategoryClassMethod {
        NSLog(@"TestCategoryClassMethod");
    }
    
    - (void)protocolMethod {
        NSLog(@"protocolMethod");
    }
    
    + (void)protocolClassMethod {
        NSLog(@"protocolClassMethod");
    }
    
    @end
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    2.1 分类的三大元素

    使用clang -rewrite-objc NSObject+TestCategoryNSObject.m将其转为C++源码,可以看到它长这样:

    // category结构体
    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;
    };
    
    // category结构体的赋值语句
    static struct _category_t _OBJC_$_CATEGORY_TestNSObject_$_TestCategoryNSObject __attribute__ ((used, section ("__DATA,__objc_const"))) = 
    {
    	"TestNSObject",
    	0, // &OBJC_CLASS_$_TestNSObject,
    	(const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_TestNSObject_$_TestCategoryNSObject,
    	(const struct _method_list_t *)&_OBJC_$_CATEGORY_CLASS_METHODS_TestNSObject_$_TestCategoryNSObject,
    	(const struct _protocol_list_t *)&_OBJC_CATEGORY_PROTOCOLS_$_TestNSObject_$_TestCategoryNSObject,
    	(const struct _prop_list_t *)&_OBJC_$_PROP_LIST_TestNSObject_$_TestCategoryNSObject,
    };
    
    // category结构体数组
    static struct _category_t *L_OBJC_LABEL_CATEGORY_$ [1] __attribute__((used, section ("__DATA, __objc_catlist,regular,no_dead_strip")))= {
    	&_OBJC_$_CATEGORY_TestNSObject_$_TestCategoryNSObject,
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    我们能看到,这三个数据就是该类分类的重点!

    • category结构体
    • category结构体的赋值语句
    • category结构体数组
      • 其中每一个元素都是该类的category结构体,因为这里我们该类只定义了这一个分类,所以只有一个元素。

    可能我们看到上面的category结构体的赋值语句其中那几个长串串数据人都傻了,别慌,下面我们慢慢来看:

    2.2 分类的对象方法列表结构体

    // 对象方法实现
    static void _I_TestNSObject_TestCategoryNSObject_TestCategoryMethod(TestNSObject * self, SEL _cmd) {
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_7n_zhjyqbvs75z54mcf1v4rtctr0000gn_T_TestNSObject_TestCategoryNSObject_630503_mi_0);
    }
    static void _I_TestNSObject_TestCategoryNSObject_protocolMethod(TestNSObject * self, SEL _cmd) {
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_7n_zhjyqbvs75z54mcf1v4rtctr0000gn_T_TestNSObject_TestCategoryNSObject_630503_mi_2);
    }
    
    // 对象方法列表结构体
    static struct /*_method_list_t*/ {
    	unsigned int entsize;  // sizeof(struct _objc_method)
    	unsigned int method_count; // 数组长度
    	struct _objc_method method_list[2]; // 用数组存储对象方法,每个元素的三个数据分别为:方法名、接收参数、以及函数指针
    } _OBJC_$_CATEGORY_INSTANCE_METHODS_TestNSObject_$_TestCategoryNSObject __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    	sizeof(_objc_method),
    	2,
    	{{(struct objc_selector *)"TestCategoryMethod", "v16@0:8", (void *)_I_TestNSObject_TestCategoryNSObject_TestCategoryMethod},
    	{(struct objc_selector *)"protocolMethod", "v16@0:8", (void *)_I_TestNSObject_TestCategoryNSObject_protocolMethod}}
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • - (void)TestCategoryMethod;- (void)protocolMethod;方法的实现
    • 对象方法结构体列表结构体

    我们可以看到,只要是在该分类中实现的对象方法(不管是协议中的还是自身的),都会添加到该分类的方法列表结构体_OBJC_$_CATEGORY_INSTANCE_METHODS_TestNSObject_$_TestCategoryNSObject中,如果我们仅仅是对方法定义而不实现,那么它就不会添加进来。

    2.3 分类的类方法列表结构体

    // 类方法的实现
    static void _C_TestNSObject_TestCategoryNSObject_TestCategoryClassMethod(Class self, SEL _cmd) {
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_7n_zhjyqbvs75z54mcf1v4rtctr0000gn_T_TestNSObject_TestCategoryNSObject_630503_mi_1);
    }
    static void _C_TestNSObject_TestCategoryNSObject_protocolClassMethod(Class self, SEL _cmd) {
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_7n_zhjyqbvs75z54mcf1v4rtctr0000gn_T_TestNSObject_TestCategoryNSObject_630503_mi_3);
    }
    
    // 类方法列表结构体
    static struct /*_method_list_t*/ {
    	unsigned int entsize;  // sizeof(struct _objc_method)
    	unsigned int method_count; // 数组长度
    	struct _objc_method method_list[2]; // 用数组存储类方法,每个元素的三个数据分别为:方法名、接收参数、以及函数指针
    } _OBJC_$_CATEGORY_CLASS_METHODS_TestNSObject_$_TestCategoryNSObject __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    	sizeof(_objc_method),
    	2,
    	{{(struct objc_selector *)"TestCategoryClassMethod", "v16@0:8", (void *)_C_TestNSObject_TestCategoryNSObject_TestCategoryClassMethod},
    	{(struct objc_selector *)"protocolClassMethod", "v16@0:8", (void *)_C_TestNSObject_TestCategoryNSObject_protocolClassMethod}}
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • + (void)TestCategoryClassMethod;+ (void)protocolClassMethod;类方法的实现
    • 类方法列表结构体

    与对象方法一样,但是它会添加到添加到类方法列表结构体_OBJC_$_CATEGORY_CLASS_METHODS_TestNSObject_$_TestCategoryNSObject中。

    2.4 分类的协议列表结构体

    // 协议结构体
    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;
    };
    
    // 分类中添加的协议列表结构体
    static struct /*_protocol_list_t*/ {
    	long protocol_count;  // Note, this is 32/64 bit
    	struct _protocol_t *super_protocols[1]; // 因为我们在定义该协议的时候,有一个的操作,所以这里就会将其加进来,类似于继承吧,所以就有一个数据
    } _OBJC_PROTOCOL_REFS_TestCategoryProtocol __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    	1,
    	&_OBJC_PROTOCOL_NSObject
    };
    
    // 分类中添加的对象方法列表结构体
    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_TestCategoryProtocol __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    	sizeof(_objc_method),
    	1,
    	{{(struct objc_selector *)"protocolMethod", "v16@0:8", 0}}
    };
    
    // 分类中添加的类方法列表结构体
    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_TestCategoryProtocol __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    	sizeof(_objc_method),
    	1,
    	{{(struct objc_selector *)"protocolClassMethod", "v16@0:8", 0}}
    };
    
    // 协议结构体赋值,等等打包给分类
    struct _protocol_t _OBJC_PROTOCOL_TestCategoryProtocol __attribute__ ((used)) = {
    	0,
    	"TestCategoryProtocol",
    	(const struct _protocol_list_t *)&_OBJC_PROTOCOL_REFS_TestCategoryProtocol,
    	(const struct method_list_t *)&_OBJC_PROTOCOL_INSTANCE_METHODS_TestCategoryProtocol,
    	(const struct method_list_t *)&_OBJC_PROTOCOL_CLASS_METHODS_TestCategoryProtocol,
    	0,
    	0,
    	0,
    	sizeof(_protocol_t),
    	0,
    	(const char **)&_OBJC_PROTOCOL_METHOD_TYPES_TestCategoryProtocol
    };
    // 将刚赋值完的结构体重命名
    struct _protocol_t *_OBJC_LABEL_PROTOCOL_$_TestCategoryProtocol = &_OBJC_PROTOCOL_TestCategoryProtocol;
    
    // 将其再打包,封装成一个协议列表结构体,到时候赋值给该分类
    static struct /*_protocol_list_t*/ {
    	long protocol_count;  // Note, this is 32/64 bit
    	struct _protocol_t *super_protocols[1]; // 因为该分类只遵循了这一个协议,所以是1
    } _OBJC_CATEGORY_PROTOCOLS_$_TestNSObject_$_TestCategoryNSObject __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    	1,
    	&_OBJC_PROTOCOL_TestCategoryProtocol //刚才打包封装的协议的结构体
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 协议的封装逻辑还是挺清晰的,先是初始化赋值一个协议结构体,然后把这个结构体封装打包,然后再把打包的结构体赋给该分类中的协议列表。这样就算该分类中有多个协议,那么我们将其协议结构体初始化后,打包给该分类的协议列表就可以了。

    2.5 分类的属性列表结构体

    // 属性结构体
    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[1]; // 存储属性的数组
    } _OBJC_$_PROP_LIST_TestNSObject_$_TestCategoryNSObject __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    	sizeof(_prop_t),
    	1,
    	{{"personName","T@\"NSString\",C,N"}} // 两个数据,一个属性名,一个属性类型及其修饰符
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 从该源码可知,我们在分类中定义的属性,它会有一个属性列表来储存,但是分类中并没有成员变量结构体(_ivar_list_t结构体),它更不会自己生成其set/get方法。

    这也说明了Category中不能添加成员变量这一事实。

    分类结构总结

    分类其实也是一个结构体,其中主要包含了以下内容:

    • _method_list_t 类型的【对象方法列表结构体】
    • _method_list_t 类型的【类方法列表结构体】
    • _protocol_list_t 类型的【协议列表结构体】
    • _prop_list_t 类型的【属性列表结构体】

    注意_category_t结构体中并不包含_ivar_list_t类型,也就是不包含【成员变量结构体】。

    三、分类Category的源码分析

    我们知道,Objective-C 的运行是依赖 Objective-C 的 Runtime 的,而 Objective-C 的 runtime 和其他系统库一样,是 OS X 和 iOS 通过 dyld 动态加载的。

    那么分类到底是怎样将数据在运行期加载到本类上去呢,这就是我们接下来要看的了。

    我们先从 runtime 的初始化函数_objc_init看起:

    1._objc_init

    /***********************************************************************
    * _objc_init
    * Bootstrap initialization. Registers our image notifier with dyld.
    * Called by libSystem BEFORE library initialization time
    **********************************************************************/
    
    void _objc_init(void)
    {
        static bool initialized = false;
        if (initialized) return;
        initialized = true;
        
        // fixme defer initialization until an objc-using image is found?
        environ_init();
        tls_init();
        static_init();
        runtime_init();
        exception_init();
    #if __OBJC2__
        cache_t::init();
    #endif
        _imp_implementationWithBlock_init();
    
        _dyld_objc_notify_register(&map_images, load_images, unmap_image);
    
    #if __OBJC2__
        didCallDyldNotifyRegister = true;
    #endif
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    其调用_dyld_objc_notify_register方法,传入map_images地址(方法地址或者函数地址),map_images读取资源(images代表资源模块):

    2.map_images

    void
    map_images(unsigned count, const char * const paths[],
               const struct mach_header * const mhdrs[])
    {
        bool takeEnforcementDisableFault;
    
        {
            mutex_locker_t lock(runtimeLock);
            map_images_nolock(count, paths, mhdrs, &takeEnforcementDisableFault);
        }
    
        if (takeEnforcementDisableFault) {
    #if TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR
            bool objcModeNoFaults = DisableFaults
                || DisableClassROFaults
                || getpid() == 1
                || is_root_ramdisk()
                || !os_variant_has_internal_diagnostics("com.apple.obj-c");
            if (!objcModeNoFaults) {
                os_fault_with_payload(OS_REASON_LIBSYSTEM,
                                      OS_REASON_LIBSYSTEM_CODE_FAULT,
                                      NULL, 0,
                                      "class_ro_t enforcement disabled",
                                      0);
            }
    #endif
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    然后在其中调用map_images_nolock方法(这个方法太长了知道就行),在map_images_nolock方法中调用_read_images方法(镜像,加载一些模块),在_read_images函数中找到与分类相关的代码,加载分类信息(分类信息是个二维数组):

    // Discover categories. 
    for (EACH_HEADER) {
    	// 获取category列表
        category_t **catlist = 
            _getObjc2CategoryList(hi, &count);
        bool hasClassProperties = hi->info()->hasCategoryClassProperties();
    
    	// 便利category列表中的每一个category
        for (i = 0; i < count; i++) {
        	// 获得当前category
            category_t *cat = catlist[i];
            // 获取该category的主类
            Class cls = remapClass(cat->cls);
    
            if (!cls) { // 没有主类,说明该分类有问题了
                catlist[i] = nil;
                if (PrintConnecting) {
                    _objc_inform("CLASS: IGNORING category \?\?\?(%s) %p with "
                                 "missing weak-linked target class", 
                                 cat->name, cat);
                }
                continue;
            }
            // 能到这就说明有主类
            bool classExists = NO;
            // 该category有 对象方法 || 协议 || 属性
            if (cat->instanceMethods ||  cat->protocols  
                ||  cat->instanceProperties) 
            {
            	// 将该分类中的 对象方法、协议、属性加到全局的分类列表中
                addUnattachedCategoryForClass(cat, cls, hi);
                if (cls->isRealized()) { // 主类中有实现
                	// 将该分类中的 对象方法、协议、属性加到该类中的对应列表中
                    remethodizeClass(cls);
                    classExists = YES;
                }
                if (PrintConnecting) {
                    _objc_inform("CLASS: found category -%s(%s) %s", 
                                 cls->nameForLogging(), cat->name, 
                                 classExists ? "on existing class" : "");
                }
            }
    		
    		// 该分类中有 类方法、协议、实现的类属性
            if (cat->classMethods  ||  cat->protocols  
                ||  (hasClassProperties && cat->_classProperties)) 
            {
            	// 将该分类中的 类方法、协议、实现的类属性加到全局的分类列表中
                addUnattachedCategoryForClass(cat, cls->ISA(), hi);
                if (cls->ISA()->isRealized()) { // 元类中有实现
                	// 将该分类中的 类方法、协议、实现的类属性加到该类的元类中的对应列表中
                    remethodizeClass(cls->ISA());
                }
                if (PrintConnecting) {
                    _objc_inform("CLASS: found category +%s(%s)", 
                                 cls->nameForLogging(), cat->name);
                }
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 1.获取category列表list
    • 2.遍历category list中的每一个category
    • 3.获取category的对应的主类cls,如果没有cls就跳过(continue)这个继续获取下一个
    • 4.如果其有对应的主类,并其有实例方法、协议、属性,则调用addUnattachedCategoryForClass,同时如果cls中有实现的话,进一步调用remethodizeClass方法
    • 5.如果其有对应的主类,并其有类方法、协议,则调用addUnattachedCategoryForClass,同时如果cls的元类有实现的话,就进一步调用remethodizeClass方法

    第4、第5步主要是对类和元类对象相应方法的区分。

    看一下其中调用的addUnattachedCategoryForClass的方法:

    3.addUnattachedCategoryForClass

    static void addUnattachedCategoryForClass(category_t *cat, Class cls, 
                                              header_info *catHeader)
    {
        runtimeLock.assertWriting();
    
        // DO NOT use cat->cls! cls may be cat->cls->isa instead
        // 获得一个全局的cats,是一个字典对象,key为cls,value为category_list *列表
        NXMapTable *cats = unattachedCategories();
        category_list *list;
    	
    	// 在全局的cats中查找有没有cls对应的category列表
        list = (category_list *)NXMapGet(cats, cls);
        if (!list) { // 如果没找到
        	// 新开辟一块空间存储该category列表
            list = (category_list *)
                calloc(sizeof(*list) + sizeof(list->list[0]), 1);
        } else { // 找到了
        	// 再多开辟一块空间,将传过来的category列表加入其中
            list = (category_list *)
                realloc(list, sizeof(*list) + sizeof(list->list[0]) * (list->count + 1));
        }
        // 将cat和catHeader打包添加进来
        list->list[list->count++] = (locstamped_category_t){cat, catHeader};
        // 将我们传递过来的category以字典的方式存储,key为cls,value为传过来的分类列表
        NXMapInsert(cats, cls, list);
    }
    
    // 返回一个全局的category字典
    static NXMapTable *unattachedCategories(void)
    {
        runtimeLock.assertWriting();
        //全局对象
        static NXMapTable *category_map = nil;
        if (category_map) return category_map;
        // fixme initial map size
        category_map = NXCreateMapTable(NXPtrValueMapPrototype, 16);
        return category_map;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 通过unattachedCategories()函数获得一个全局对象cats,key为cls,value为category_list *
    • 我们从这个全局字典中查找cls,获取一个category_list *list列表
    • 要是没有list列表,那么我们就生成一个category_list空间,将这组键(cls)值(cat、catHeader)对插入
    • 要是有list列表,我们就在该列表的基础上再分配出一个category_list大小的空间,将这组键(cls)值(cat、catHeader)对插入

    等于说这个方法就是将我们分类中的方法添加到一个全局的map中,把类和category做一个关联映射,并且该map的keyclsvaluecategory_list *,即一个类对应一个分类列表,一个category_list *包含了很多分类category_t *,每个分类category_t *中又存储了该分类的各种数据(分类名、所属类、对象方法、类方法等等)。

    4.remethodizeClass

    static void remethodizeClass(Class cls)
    {
        // 分类数组
        category_list *cats;
        bool isMeta;
    
        runtimeLock.assertWriting();
    
    	// 该类是不是元类
        isMeta = cls->isMetaClass();
    
        // Re-methodizing: check for more categories
        // 获取该类对应的分类列表
        if ((cats = unattachedCategoriesForClass(cls, false/*not realizing*/))) {
            if (PrintConnecting) {
                _objc_inform("CLASS: attaching categories to class '%s' %s", 
                             cls->nameForLogging(), isMeta ? "(meta)" : "");
            }
            // 这个才是核心
            attachCategories(cls, cats, true /*flush caches*/);        
            free(cats);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    该方法只是得到该类的分类列表,然后将其给attachCategories方法。

    5.attachCategories

    static void 
    attachCategories(Class cls, category_list *cats, bool flush_caches)
    {
        if (!cats) return;
        if (PrintReplacedMethods) printReplacements(cls, cats);
    
        bool isMeta = cls->isMetaClass();
    
        // 创建方法列表、属性列表、协议列表,用来存储分类列表中的所有方法、属性、协议
        method_list_t **mlists = (method_list_t **)
            malloc(cats->count * sizeof(*mlists));
        property_list_t **proplists = (property_list_t **)
            malloc(cats->count * sizeof(*proplists));
        protocol_list_t **protolists = (protocol_list_t **)
            malloc(cats->count * sizeof(*protolists));
    
        // Count backwards through cats to get newest categories first
        int mcount = 0;           // 记录方法的数量
        int propcount = 0;        // 记录属性的数量
        int protocount = 0;       // 记录协议的数量
        int i = cats->count;      // 从分类数组最后开始遍历,保证先取的是最新的分类
        bool fromBundle = NO;     // 记录是否是从 bundle 中取的
        while (i--) { // 从后往前依次遍历
            auto& entry = cats->list[i];  // 取出当前分类
        
            // 取出分类中的方法列表。如果是元类,取得的是类方法列表;否则取得的是对象方法列表
            method_list_t *mlist = entry.cat->methodsForMeta(isMeta);
            if (mlist) { // 有方法列表
                mlists[mcount++] = mlist;            // 将方法列表放入 mlists 方法列表数组中
                fromBundle |= entry.hi->isBundle();  // 分类的头部信息中存储了是否是 bundle,将其记住
            }
    
            // 取出分类中的属性列表,如果是元类,取得的是 nil
            property_list_t *proplist = 
                entry.cat->propertiesForMeta(isMeta, entry.hi);
            if (proplist) { // 有属性列表
                proplists[propcount++] = proplist; // 将属性列表加到proplists属性列表数组中
            }
    
            // 取出分类中遵循的协议列表
            protocol_list_t *protolist = entry.cat->protocols;
            if (protolist) { // 有协议列表
                protolists[protocount++] = protolist; // 将协议列表加到protolists协议列表数组中
            }
        }
        // 上面的代码是将分类中存储的方法、属性、协议插入到各自对应的大数组中(方便等等插入对应的本类中的各种列表中),并且从后往前插,保证最终调用方法的时候会最先调用到最新的分类中的方法。
        
    
        // 取出当前类 cls 的 class_rw_t 数据
        auto rw = cls->data();
    
        // 将存储方法、属性、协议数组到本类的 rw 结构体中
        // 准备方法列表 mlists 中的方法,使用该方法实现本类中存储方法的序列化,因为方法的查找都是使用二分查找的,我们将其先排序再插入就到时候就不用麻烦了
        prepareMethodLists(cls, mlists, mcount, NO, fromBundle);
        // 将新方法列表添加到本类的 rw 的方法列表中
        rw->methods.attachLists(mlists, mcount);
        // 释放方法列表 mlists
        free(mlists);
        // 清除 cls 的缓存列表
        if (flush_caches  &&  mcount > 0) flushCaches(cls);
    
        // 将新属性列表添加到本类的 rw 的属性列表中
        rw->properties.attachLists(proplists, propcount);
        // 释放属性列表
        free(proplists);
    
        // 将新协议列表添加到本类的 rw 的协议列表中
        rw->protocols.attachLists(protolists, protocount);
        // 释放协议列表
        free(protolists);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 该方法主要就是将我们分类中的信息添加到本类的class_rw_t结构体中,就等于说是这个分类现在就没有作用了(也不能这样说,就是这个意思),分类中的方法、属性、协议都已经存储到本类中了,直接就能在本类调用了。

    6. attachLists

    attachLists方法保证新的数据添加到列表的前面:

    void attachLists(List* const * addedLists, uint32_t addedCount) {
        if (addedCount == 0) return;
    
        if (hasArray()) { // 原列表中有很多数据,还要添加很多数据
            // many lists -> many lists
            uint32_t oldCount = array()->count; // 获取旧数据长度
            uint32_t newCount = oldCount + addedCount; // 得到更新后的数据长度
            setArray((array_t *)realloc(array(), array_t::byteSize(newCount))); // 开辟新数据长度的空间
            array()->count = newCount; // 更新数据长度
            memmove(array()->lists + addedCount, array()->lists, 
                    oldCount * sizeof(array()->lists[0])); // 将旧数据都往后移动新加数据的长度,保证旧数据在新数据的后面
            memcpy(array()->lists, addedLists, 
                   addedCount * sizeof(array()->lists[0])); // 将新数据插在前面
        }
        else if (!list  &&  addedCount == 1) { // 原列表中没有数据,要添加一个数据
            // 0 lists -> 1 list
            // 那就直接让原列表指向这个新添数据就完了
            list = addedLists[0];
        } 
        else { // 原列表中只有一个数据,要添加多个数据
            // 1 list -> many lists
            List* oldList = list; // 获取原列表
            uint32_t oldCount = oldList ? 1 : 0; // 如果原列表中有数据,其长度肯定为1,否则就为0
            uint32_t newCount = oldCount + addedCount; // 获取添加数据后的总长度
            setArray((array_t *)malloc(array_t::byteSize(newCount))); // 开辟更新后的长度
            array()->count = newCount; // 设置长度值
            if (oldList) array()->lists[addedCount] = oldList; // 将旧数据移到最后
            memcpy(array()->lists, addedLists, 
                   addedCount * sizeof(array()->lists[0])); // 将新数据插在前面
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    一图说明这个方法是在干什么:
    5345345

    那么它是怎么保证新数据一定在旧数据的前面呢?

    // memmove :内存移动
    void    *memmove(void *__dst, const void *__src, size_t __len);
    /*
       __dst : 移动内存的目的地
       __src : 被移动的内存首地址
       __len : 被移动的内存长度
       将__src的内存移动__len块内存到__dst中
    */
    
    // memcpy :内存拷贝
    void    *memcpy(void *__dst, const void *__src, size_t __n);
    /*
       __dst : 拷贝内存的拷贝目的地
       __src : 被拷贝的内存首地址
       __n : 被移动的内存长度
       将__src的内存移动__n块内存到__dst中
    */
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    memmove之后:虽然本类的方法,属性,协议列表指针的地址会分别后移,但是本类的对应数组的指针依然指向原始位置:
    4234234234
    memcpy后:原来指针并没有改变,至始至终指向开头的位置。并且经过memmove和memcpy方法之后,分类的方法,属性,协议列表被放在了类对象中原本存储的方法,属性,协议列表前面:
    5345345
    所以总体来说就是先将原方法移动到后面,再将分类方法拷贝进来。

    • array()->lists: 类对象原来的方法列表,属性列表,协议列表,比如Person中的那些方法等
    • addedLists:传入所有分类的方法列表,属性列表,协议列表,比如Person(Eat)、Person(Drink)中的那些方法等。

    上面代码的作用就是通过memmove将原来的类找那个的方法、属性、协议列表分别进行后移,然后通过memcpy将传入的方法、属性、协议列表填充到开始的位置。

    • 分类的方法、属性、协议只是添加到原有类上,并没有将原有类的方法、属性、协议进行完全替换
      • 举个例子说明就是:假设原有类拥有 MethodA方法,分类也拥有 MethodA 方法,那> 么加载完分类之后,类的方法列表中会拥有两个 MethodA方法。
    • 分类的方法、属性、协议会添加到原有类的方法列表、属性列表、协议列表的最前面,而原有类的方法列表、属性列表、协议列表则被移动到了列表的后面
      • 因为运行时查找方法是顺着方法列表的顺序进行依次查找的,所以Category的方法会先被搜索到,然后直接指向,而原有类的方法则不被指向。这也是分类中的方法会覆盖掉原有类的方法最直接的原因。

    总结一下分类是怎么实现方法添加的:

    • 首先通过分类的结构体存储各个分类中的方法、协议、属性
    • 然后在运行期时候调用相关方法,再将分类结构体中的内容都存入相关的全局category map中,以clskey,以category_list *列表为value
    • 最后再通过cls找到全局category map中对应的分类数据,把分类的数据序列化加入本类中的class_rw_t结构体中,并且保证新加的数据都是在旧数据的前面的

    为什么分类不能添加成员变量?

    这里需要说明一下,我们的分类为什么不能添加成员变量,这是因为分类结构中并没有成员变量列表的存储属性,分类在运行时被attach添加到类,是对rw的处理,在class_rw_t *rw中,并没有const ivar_list_t *ivars成员变量列表属性!!!这个成员变量列表属性是在class_ro_t *ro中处理的!所以我们不能在分类中添加成员变量,但是可以添加属性!!!

    四、load方法和initialize方法

    直接看结论吧,感觉没啥说的

    1.load方法的调用顺序

    load方法的调用时机,在runtime加载类、分类时由系统调用的。
    每个类、分类的load在程序运行过程中只调用一次load,除非自己再手动调用load才会再次调用。

    • 先调用类的load方法
      • 按照编译的先后顺序调用(先编译的先调用)
      • 调用子类的load方法之前会先调用父类的load方法
    • 再调用分类的load方法
      • 按照编译的先后顺序调用(先编译的先调用)

    系统调用load方法它是直接找到类的load方法的地址,然后调用load方法,然后再找到分类的load方法,再去调用。
    而对于手动调用load,则通过msgSend方式,找到所属的类的元类对象,如果分类也实现了load,则调用分类的load,因为分类中的方法总是加载本类方法的前面。

    对于“覆盖”的方法,会找到最后一个编译的方法,和我们上面理解的分类实现的方法一样。

    2.initialize方法调用顺序

    类第一次接收消息时,会调用initialize(初始化)。
    通过msgSend调用。

    • 当父类没有initialize时,先调用父类的initialize,再调用当前类的initialize
    • 如果当前类没有实现initialize,则调用的是父类的initialize
    • 若多个子类未实现都未实现initialize方法,则父类的initialize方法会被调用多次
    • 当分类实现了initialize,会覆盖本类的initialize

    并且我们在写initialize方法时,不需要调用父类的initialize方法,系统会自动调用,如果你在调用的话,就会多次调用分类的initialize方法了。

    load和initialize区别

    • 调用时机
      load,当runtime加载类、分类时会调用。load方法总是在main函数之前调用,每个类、分类的load在运行时只调用一次
      initialize,在类第一次接收到消息时调用(先初始化父类,再初始化子类,每一个类只会被初始化一次)
    • 调用顺序
      load方法:先调用类的load,子类调用load方法之前会先调用父类的load,先编译的先调用;再调用分类的load方法,先编译的先调用
      initialize方法:先调用父类的initialize再调用当前类的initialize,如果子类没有实现initialize,则会调用父类的initialize;如果有分类,则调用最后编译的分类的initialize,就不调用本类的initialize了
    • 调用本质
      load,根据IMP地址直接调用(*load_method)(cls, SEL_load)
      initialize,通过objc_msgSend进行调用
    • 使用场景
      在load方法中实现方法交换(Method Swizzle)
      一般用于初始化全局变量或静态变量
    • 相同点
      两个方法会被自动调用,不需要手动调用他们
    • 区别
      load是通过直接函数地址调用,只会调用一次
      initialize是通过msgSend调用
      • 如果子类没有实现initialize,会调用父类的initialzie(所以父类的initialize会被调用很多次)
      • 分类如果实现了initialize,就会覆盖类本身的initailize

    34234234

    五、关联对象

    上面也说了,分类中不可以添加实例变量,但是可以添加属性,但是属性不能自动生成set、get方法,那么我们要怎么实现在分类中添加属性呢,这就需要关联对象出马了!

    1.关联对象的使用

    关联对象也就只有三个接口方法,分别是objc_setAssociatedObjectobjc_getAssociatedObjectobjc_removeAssociatedObjects

    1.1 objc_setAssociatedObject

    OBJC_EXPORT void
    objc_setAssociatedObject(id _Nonnull object, const void * _Nonnull key,
                             id _Nullable value, objc_AssociationPolicy policy)
        OBJC_AVAILABLE(10.6, 3.1, 9.0, 1.0, 2.0);
    
    • 1
    • 2
    • 3
    • 4

    参数一:给哪个对象添加属性,如果要给自己添加属性,使用self就行了
    参数二:设置关联对象的key,根据key获取关联对象的属性的值,就跟字典一样
    参数三:关联的值,也就是我们想要存储的值
    参数四:策略,属性以什么形式保存,类似于修饰符

    策略有以下几种:

    typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) {
        OBJC_ASSOCIATION_ASSIGN = 0,           // 指定一个弱引用相关联对象
        OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, // 指定相关的对象的强引用,非原子性
        OBJC_ASSOCIATION_COPY_NONATOMIC = 3,   // 指定相关的对象被复制,非原子性
        OBJC_ASSOCIATION_RETAIN = 01401,       // 指定相关的对象的强引用,原子性
        OBJC_ASSOCIATION_COPY = 01403          // 指定相关的对象被复制,原子性
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    423423432

    1.2 objc_getAssociatedObject

    OBJC_EXPORT id _Nullable
    objc_getAssociatedObject(id _Nonnull object, const void * _Nonnull key)
        OBJC_AVAILABLE(10.6, 3.1, 9.0, 1.0, 2.0);
    
    • 1
    • 2
    • 3

    参数一:获取哪个对象里面的关联的属性
    参数二:获取的是那个属性,字典的key,通过key去查找

    1.3 objc_removeAssociatedObjects

    OBJC_EXPORT void
    objc_removeAssociatedObjects(id _Nonnull object)
        OBJC_AVAILABLE(10.6, 3.1, 9.0, 1.0, 2.0);
    
    • 1
    • 2
    • 3

    就一个参数,移除某个对象的全部关联对象。

    举例:

    我们给上面我们说的分类中的@property (nonatomic, copy) NSString *personName;添加关联对象:

    - (void)setPersonName:(NSString *)personName {
        objc_setAssociatedObject(self, "personName", personName, OBJC_ASSOCIATION_COPY_NONATOMIC);
    }
    
    - (NSString *)personName {
        id temp = objc_getAssociatedObject(self, "personName");
        if (temp) {
            return temp;
        } else {
            return nil;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    我们通过personName作为该对象的key值,通过这个key值就可以存储和访问这个属性存储的值,这样我们也就实现了给分类中添加实例变量的功能了。

    2.关联对象的底层结构

    关联对象的核心技术有:

    • AssociationsManager
    • AssociationsHashMap
    • ObjectAssociationMap
    • ObjcAssociation

    直接逐一分析:

    2.1 AssociationsManager

    class AssociationsManager {
        // associative references: object pointer -> PtrPtrHashMap.
        // AssociationsManager中只有一个变量AssociationsHashMap
        static AssociationsHashMap *_map; // 它才是核心,这个类只是对它的封装,它是个单例
    public:
        // 构造函数中加锁
        AssociationsManager()   { AssociationsManagerLock.lock(); }
        // 析构函数中释放锁
        ~AssociationsManager()  { AssociationsManagerLock.unlock(); }
        // 构造函数、析构函数中加锁、释放锁的操作,保证了AssociationsManager是线程安全的
        
        AssociationsHashMap &associations() {
            // AssociationsHashMap 的实现可以理解成单例对象
            if (_map == NULL)
                _map = new AssociationsHashMap();
            return *_map;
        }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    2.2 AssociationsHashMap

    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); }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5

    我们发现这个玩意是继承自unordered_map,也就是说,他也是一个map,并且keydisguised_ptr_tvalueObjectAssociationMap *

    2.3 ObjectAssociationMap

    // ObjectAssociationMap是字典,key是从外面传过来的key,例如@selector(hello),value是关联对象,也就是
    // ObjectAssociation
    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); }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    我们发现这个也是一个map,其中keyvoid *valueObjcAssociation

    2.4 ObjcAssociation

    class ObjcAssociation {
        uintptr_t _policy;
        // 值
        id _value;
    public:
        // 构造函数
        ObjcAssociation(uintptr_t policy, id value) : _policy(policy), _value(value) {}
        // 默认构造函数,参数分别为0和nil
        ObjcAssociation() : _policy(0), _value(nil) {}
    
        uintptr_t policy() const { return _policy; }
        id value() const { return _value; }
        
        bool hasValue() { return _value != nil; }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    来了来了,终于找到值了,它才是真正存储关联对象值的地方,其中_value存储的是关联对象的值,_policy存储的是关联对象的策略。

    那接下来我们使用图来总结一下关联对象的结构(这个图真的是太清晰了):
    4234234
    通过对上述结构的了解,我们发现:

    • 关联对象并不是存储在被关联对象本身内存中
    • 关联对象存储在全局的统一的一个AssociationsManager
    • object作为key,一个被关联对象的所有关联对象都存储在同一个ObjectAssociationMap
    • object被关联的对象不能为nil
    • 设置关联对象valuenil,就相当于是移除关联对象
    • 关联对象巧妙的机构:
      • 它是通过两层map来存储我们要关联的对象的
      • 第一层就是通过一个类obj作为key,获取这个类的所有关联对象

        如果有很多类都有关联对象,那么我们就可以通过不同的object来访问各个类中设置的关联对象。

      • 第二层就是这个类中,我们自己设置的字符串或者什么的作为key,获取这个对象的值

        如果该类有很多关联对象,那么我们又可以通过我们自己设置的不同的key值,来访问我们存储的值

    分析完了关联对象的底层结构,我们在来看看它的三种方法:

    3.关联对象的底层原理

    3.1 objc_setAssociatedObject

    void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy) {
        _object_set_associative_reference(object, (void *)key, value, policy);
    }
    
    • 1
    • 2
    • 3

    它调用了_object_set_associative_reference这个方法:

    // 该方法完成了设置关联对象的操作
    void _object_set_associative_reference(id object, void *key, id value, uintptr_t policy) {
        // retain the new value (if any) outside the lock.
        // 初始化空的ObjcAssociation(关联对象)
        ObjcAssociation old_association(0, nil);
        // 如果value存在,就将value和policy进行打包,方便后续的存储
        id new_value = value ? acquireValue(value, policy) : nil;
        {
            // 初始化一个manager
            AssociationsManager manager;
            // 获取其中的单例AssociationsHashMap,现在就有了全局的关联对象表
            AssociationsHashMap &associations(manager.associations());
            // 获取对象的DISGUISE值,作为AssociationsHashMap的key
            disguised_ptr_t disguised_object = DISGUISE(object);
            if (new_value) { // 如果封装后的value有值,不为nil
                // break any existing association.
                // AssociationsHashMap::iterator 类型的迭代器
                // 以DISGUISE值为key,找这个类的关联对象map
                AssociationsHashMap::iterator i = associations.find(disguised_object);
                if (i != associations.end()) { // 没到end,就说明找到了,即存在这个object的关联对象map
                    // secondary table exists
                    // 获取到ObjectAssociationMap(key是外部传来的key,value是关联对象类ObjcAssociation)
                    ObjectAssociationMap *refs = i->second;
                    // ObjectAssociationMap::iterator 类型的迭代器
                    // 再以我们自己定义并传进来的key作为key,查找我们保存的对象值
                    ObjectAssociationMap::iterator j = refs->find(key);
                    if (j != refs->end()) { // 如果没到end,即找到了,就说明之前有存储过
                        // 那么就将原关联对象的值存起来,并且赋新值
                        old_association = j->second;
                        j->second = ObjcAssociation(policy, new_value);
                    } else { // 如果到end了,即没找到,就说明之前没有存储过
                        // 无该key对应的关联对象,直接赋值保存即可
                        // ObjcAssociation(policy, new_value)提供了这样的构造函数
                        (*refs)[key] = ObjcAssociation(policy, new_value);
                    }
                } else { // 即第一层遍历到end了,通过object找不到对应的关联对象map
                    // create the new association (first time).
                    // 执行到这里,说明该对象是第一次添加关联对象,直接新建添加就完事了
                    // 初始化ObjectAssociationMap
                    ObjectAssociationMap *refs = new ObjectAssociationMap;
                    // 第一层map赋值
                    associations[disguised_object] = refs;
                    // 第二层map赋值
                    (*refs)[key] = ObjcAssociation(policy, new_value);
                    // 设置该对象的有关联对象,调用的是setHasAssociatedObjects()方法
                    object->setHasAssociatedObjects();
                }
            } else { // 如果封装后的value为nil
                // setting the association to nil breaks the association.
                // value无值,也就是释放一个key对应的关联对象
                // 通过迭代器找对应的map,找不到就说明原来也没有,也就不用管了
                AssociationsHashMap::iterator i = associations.find(disguised_object);
                if (i !=  associations.end()) { // 找到了
                	// 获取第二层map
                    ObjectAssociationMap *refs = i->second;
                    // 通过key在第二层中找到对应的关联对象的值,没找到也就不管了,因为本来也就是要删除这个关联对象的
                    ObjectAssociationMap::iterator j = refs->find(key);
                    if (j != refs->end()) { // 找到了
                    	// 先将旧值保存下来
                        old_association = j->second;
                        // 调用erase()方法删除对应的关联对象
                        refs->erase(j);
                    }
                }
            }
        }
        // release the old value (outside of the lock).
        // 统一释放旧的关联对象
        if (old_association.hasValue()) ReleaseValue()(old_association);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70

    首先根据我们传入的value经过acquireValue函数处理获取new_valueacquireValue函数内部其实时通过对策略的判断返回不同的值:

    // 根据policy的值,对value进行retain或者copy
    static id acquireValue(id value, uintptr_t policy) {
        switch (policy & 0xFF) {
        case OBJC_ASSOCIATION_SETTER_RETAIN:
            return objc_retain(value);
        case OBJC_ASSOCIATION_SETTER_COPY:
            return ((id(*)(id, SEL))objc_msgSend)(value, SEL_copy);
        }
        return value;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    之后创建AssociationsManager manager,以及拿到Manager内部的AssociationsHashMap,对应代码就是associations

    AssociationsHashMap &associations() {
        // AssociationsHashMap 的实现可以理解成单例对象
        if (_map == NULL)
            _map = new AssociationsHashMap();
        return *_map;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    之后我们看到传入的第一个参数objectobject经过DISGUISE函数被转换成了disguised_ptr_t类型的disguised_object

    typedef uintptr_t disguised_ptr_t;
    inline disguised_ptr_t DISGUISE(id value) { return ~uintptr_t(value); }
    inline id UNDISGUISE(disguised_ptr_t dptr) { return id(~dptr); }
    
    • 1
    • 2
    • 3

    这里只对其做了简单的编码处理,直接取反了。

    3.2 objc_getAssociatedObject

    // 获取关联对象的方法
    id objc_getAssociatedObject(id object, const void *key) {
        return _object_get_associative_reference(object, (void *)key);
    }
    
    • 1
    • 2
    • 3
    • 4

    其中调用了_object_get_associative_reference方法:

    // 获取关联对象
    id _object_get_associative_reference(id object, void *key) {
        id value = nil; // 用于保存等等获取的值
        uintptr_t policy = OBJC_ASSOCIATION_ASSIGN; // 用于保存等等获取的策略
        {
        	// 初始化一个AssociationsManager
            AssociationsManager manager;
            // 获取到manager中的单例AssociationsHashMap
            AssociationsHashMap &associations(manager.associations());
            // 获取对象的DISGUISE值
            disguised_ptr_t disguised_object = DISGUISE(object);
            // 通过对象的DISGUISE值查找对象对应的map
            AssociationsHashMap::iterator i = associations.find(disguised_object);
            if (i != associations.end()) { // 找到了
                // 获取ObjectAssociationMap,即获取第二层map
                ObjectAssociationMap *refs = i->second;
                // 在第二层map中通过key查找准确的值
                ObjectAssociationMap::iterator j = refs->find(key);
                if (j != refs->end()) { // 找到了
                    // 获取到关联对象ObjcAssociation
                    ObjcAssociation &entry = j->second;
                    // 获取到value
                    value = entry.value();
                    // 获取到policy
                    policy = entry.policy();
                    // 通过相应的策略返回相对应的修饰属性
                    if (policy & OBJC_ASSOCIATION_GETTER_RETAIN) {
                        objc_retain(value);
                    }
                }
            }
        }
        // 通过相应的策略返回相对应的修饰属性
        if (value && (policy & OBJC_ASSOCIATION_GETTER_AUTORELEASE)) {
            objc_autorelease(value);
        }
        // 返回关联对像的值
        return value;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39

    很简单,就是通过传入的objectkey进行两层map的查找,找到了最终再根据策略返回就是了。

    3.3 objc_removeAssociatedObjects

    // 移除对象object的所有关联对象
    void objc_removeAssociatedObjects(id object) 
    {
    	// 判断对象是否有关联对象,有关联对象才会执行接下来的移除操作
        if (object && object->hasAssociatedObjects()) {
            _object_remove_assocations(object);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    它调用了_object_remove_assocations方法:

    // 移除对象object的所有关联对象
    void _object_remove_assocations(id object) {
        // 声明了一个vector,用于之后的便利删除,它也是两层结构
        vector<ObjcAssociation, ObjcAllocator<ObjcAssociation>> elements;
        {	
        	// 初始化AssociationsManager
            AssociationsManager manager;
            // 获取其中的单例map
            AssociationsHashMap &associations(manager.associations());
            // 如果map size为空,直接返回
            if (associations.size() == 0) return;
            // 获取对象的DISGUISE值
            disguised_ptr_t disguised_object = DISGUISE(object);
            // 通过对象的DISGUISE进行查找
            AssociationsHashMap::iterator i = associations.find(disguised_object);
            if (i != associations.end()) { // 找到了
                // copy all of the associations that need to be removed.
                // 获取第二层map
                ObjectAssociationMap *refs = i->second;
                // 将第二层map中的内容遍历加入到刚才定义的数组中
                for (ObjectAssociationMap::iterator j = refs->begin(), end = refs->end(); j != end; ++j) {
                    elements.push_back(j->second);
                }
                // remove the secondary table.
                // 删除这个第二层的map
                delete refs;
                // 释放第一层的对应键值对
                associations.erase(i);
            }
        }
        // the calls to releaseValue() happen outside of the lock.
        // 遍历删除
        for_each(elements.begin(), elements.end(), ReleaseValue());
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    到这里就结束了,重点还是那张图,把那张图看懂了,关联对象不轻轻松松学会了。

  • 相关阅读:
    activiti-image-generator
    智能机器人与AI助手:未来办公场景的变革者
    spark调优案例分享
    【学习笔记】Linux文件编译调试相关(问题未解决)
    天呐,我居然可以隔空作画了
    Vue3语法糖setup(二)
    在express中使用session认证
    [MAUI]深入了解.NET MAUI Blazor与Vue的混合开发
    【高阶数据结构】并查集和图
    大语言模型助力审计问题自动定性
  • 原文地址:https://blog.csdn.net/m0_55124878/article/details/126171286