[iOS开发]Category、Extension和关联对象

656 阅读18分钟

什么是Category?

  • Category是Objective-C 2.0之后添加的语言特性,分类、类别其实都是指的Category。
  • Category的主要作用是为已经存在的类添加方法。也可以说是将庞大的类代码按逻辑划入几个分区。
  • 分类的特性是可以在运行时阶段动态的为已有的类添加新行为
  • Objective-C 中Category 就是对装饰模式的一种具体实现。它的主要作用是在不改变原有类的前提下,动态地给这个类添加一些方法。

分类和扩展

  • 扩展(Extension)有时候被称为匿名分类。但是两者实质上不是一个内容。
  • 扩展是在编译阶段与该类同时编译的,是类的一部分。扩展中声明的方法只能在该类的@implementation中实现。所以这也就意味着我们无法对系统的类使用扩展。
  • 同时与分类不同,扩展不但可以声明方法,还可以声明成员变量,这是分类所做不到的。

Category的实质

Category结构体

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);
};

分类的结构体中可以为类添加对象方法、类方法、协议、属性,但是并没有成员变量

从C++开始看起

我们将分类的.m文件转成c++文件来了解一下

我们先搞个分类

@interface NSObject (testCategory) <TestProtocol>

@property (nonatomic, copy) NSString *personName;

- (void)test;

+ (void)secondTest;

@end

声明一个实例方法、一个类方法、一个属性 分类遵循一个协议 协议里也是一个类方法和对象方法

@protocol TestProtocol <NSObject>

- (void)protocolMethod;

+ (void)protocolClassMethod;

@end

clang -rewrite-objc NSObject+testCategory.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_NSObject_$_testCategory __attribute__ ((used, section ("__DATA,__objc_const"))) = 
{
	"NSObject",
	0, // &OBJC_CLASS_$_NSObject,
	(const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_NSObject_$_testCategory,
	(const struct _method_list_t *)&_OBJC_$_CATEGORY_CLASS_METHODS_NSObject_$_testCategory,
	(const struct _protocol_list_t *)&_OBJC_CATEGORY_PROTOCOLS_$_NSObject_$_testCategory,
	(const struct _prop_list_t *)&_OBJC_$_PROP_LIST_NSObject_$_testCategory,
};

【结构体数组】
static struct _category_t *L_OBJC_LABEL_CATEGORY_$ [1] __attribute__((used, section ("__DATA, __objc_catlist,regular,no_dead_strip")))= {
	&_OBJC_$_CATEGORY_NSObject_$_testCategory,
};
  • 我们可以看到重点的三个元素
    • category结构体
    • category结构体的赋值语句
    • category结构体数组

对象方法列表结构体

【对象方法的实现】
static void _I_NSObject_testCategory_test(NSObject * self, SEL _cmd) {
    printf("正在打印test方法");
}
static void _I_NSObject_testCategory_protocolMethod(NSObject * self, SEL _cmd) {
    printf("正在打印协议对象方法");
}

【对象方法列表结构体】
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_NSObject_$_testCategory __attribute__ ((used, section ("__DATA,__objc_const"))) = {
	sizeof(_objc_method),
	2,
	{{(struct objc_selector *)"test", "v16@0:8", (void *)_I_NSObject_testCategory_test},
	{(struct objc_selector *)"protocolMethod", "v16@0:8", (void *)_I_NSObject_testCategory_protocolMethod}}
};
  • - (void)test- (void)protocolMethod方法的实现
  • 对象方法结构体列表结构体

只要是在Category中实现了的对象方法(包括代理中的对象方法)。都会添加到对象方法列表结构体_OBJC_$_CATEGORY_INSTANCE_METHODS_NSObject_$_testCategory中来,如果仅仅是定义,没有实现,不会加进来

类方法列表结构体

【类方法的实现】
static void _C_NSObject_testCategory_secondTest(Class self, SEL _cmd) {

    printf("正在打印secondTest方法");
}

static void _C_NSObject_testCategory_protocolClassMethod(Class self, SEL _cmd) {
    printf("正在打印类对象方法");
}

【类方法列表结构体】
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_NSObject_$_testCategory __attribute__ ((used, section ("__DATA,__objc_const"))) = {
	sizeof(_objc_method),
	2,
	{{(struct objc_selector *)"secondTest", "v16@0:8", (void *)_C_NSObject_testCategory_secondTest},
	{(struct objc_selector *)"protocolClassMethod", "v16@0:8", (void *)_C_NSObject_testCategory_protocolClassMethod}}
};
  • + (void)secondTest+protocolClassMethod类方法的实现
  • 类方法列表结构体

只要是Category中实现了的类方法(包括代理中的类方法)。都会添加到类方法列表结构体_OBJC_$_CATEGORY_CLASS_METHODS_NSObject_$_testCategory中来

协议列表结构体

【协议结构体】
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];//为什么是1?
} _OBJC_PROTOCOL_REFS_TestProtocol __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_TestProtocol __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_TestProtocol __attribute__ ((used, section ("__DATA,__objc_const"))) = {
	sizeof(_objc_method),
	1,
	{{(struct objc_selector *)"protocolClassMethod", "v16@0:8", 0}}
};

【结构体赋值】
struct _protocol_t _OBJC_PROTOCOL_TestProtocol __attribute__ ((used)) = {
	0,
	"TestProtocol",
	(const struct _protocol_list_t *)&_OBJC_PROTOCOL_REFS_TestProtocol,
	(const struct method_list_t *)&_OBJC_PROTOCOL_INSTANCE_METHODS_TestProtocol,
	(const struct method_list_t *)&_OBJC_PROTOCOL_CLASS_METHODS_TestProtocol,
	0,
	0,
	0,
	sizeof(_protocol_t),
	0,
	(const char **)&_OBJC_PROTOCOL_METHOD_TYPES_TestProtocol
};
struct _protocol_t *_OBJC_LABEL_PROTOCOL_$_TestProtocol = &_OBJC_PROTOCOL_TestProtocol;
  • 协议列表结构体
  • 协议列表 对象方法列表结构体
  • 协议列表 类方法列表结构体
  • 结构体赋值语句

属性列表结构体

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_NSObject_$_testCategory __attribute__ ((used, section ("__DATA,__objc_const"))) = {
	sizeof(_prop_t),
	1,
	{{"personName","T@\"NSString\",C,N"}}
};

从【属性列表结构体】源码中我们可以看到:只有person分类中添加的属性列表结构体_OBJC_$_PROP_LIST_NSObject_$_testCategory,没有成员变量结构体_ivar_list_t结构体。更没有对应的set/get方法相关的内容。 这也说明了Category中不能添加成员变量这一事实。

category总结

主要包含下面几部分内容

  1. _method_list_t 类型的【对象方法列表结构体】;
  2. _method_list_t 类型的【类方法列表结构体】;
  3. _protocol_list_t 类型的【协议列表结构体】;
  4. _prop_list_t 类型的【属性列表结构体】。

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

分类在运行期做了什么

想搞明白这个问题,我们就要知道什么时候调用了分类的方法

_objc_init这个函数是runtime的初始化函数,我们从_objc_init开始入手

_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();
    lock_init();
    exception_init();

    _dyld_objc_notify_register(&map_images, load_images, unmap_image);
}

map_images读取资源(images代表资源模块),来到map_images_nolock函数中找到_read_images函数,在_read_images函数中找到与分类相关的代码

_read_images

// Discover categories. 
    for (EACH_HEADER) {
        category_t **catlist = 
            _getObjc2CategoryList(hi, &count);
        bool hasClassProperties = hi->info()->hasCategoryClassProperties();

        for (i = 0; i < count; i++) {
            category_t *cat = catlist[i];
            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;
            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);
                }
            }
        }
    }
  • 获取category列表list
  • 遍历category list中的每一个category
  • 获取category的对应的主类cls,如果没有cls就跳过(continue)这个继续获取下一个
  • 如果其有对应的主类,并其有实例方法、协议、属性,则调用addUnattachedCategoryForClass,同时如果cls中有实现的话,进一步调用remethodizeClass方法
  • 如果其有对应的主类,并其有类方法、协议,则调用addUnattachedCategoryForClass,同时如果cls的元类有实现的话,就进一步调用remethodizeClass方法

4、5主要是分类对应的主类是元类对象还是类对象

看一下对应的addUnattachedCategoryForClass的方法

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
    NXMapTable *cats = unattachedCategories();
    category_list *list;

    list = (category_list *)NXMapGet(cats, cls);
    if (!list) {
        list = (category_list *)
            calloc(sizeof(*list) + sizeof(list->list[0]), 1);
    } else {
        list = (category_list *)
            realloc(list, sizeof(*list) + sizeof(list->list[0]) * (list->count + 1));
    }
    list->list[list->count++] = (locstamped_category_t){cat, catHeader};
    NXMapInsert(cats, cls, list);
}
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;
}
  • 通过unattachedCategories()函数生成一个全局对象cats
  • 我们从这个单例对象中查找cls,获取一个category_list *list列表
  • 要是没有list指针。那么我们就生成一个category_list空间
  • 要是有list指针,我们就在该指针的基础上再分配处category_list大小的空间
  • 在这新分配好的空间,将这个catcatHeader写入
  • 将数据插入到cats中,key----->cls value------>list

这段代码对于我们来说,对分类的实现部分关系并不大,其仅仅是把类和category做一个关联映射,而remethodizeClass才是真正去处理添加事宜的功臣

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);
    }
}

还是没有得到我们需要的信息,其核心是调用了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;
        }

        // 取出分类中遵循的协议列表
        protocol_list_t *protolist = entry.cat->protocols;
        if (protolist) {
            protolists[protocount++] = protolist;
        }
    }
    上面部分的代码仅仅是将分类中的方法、属性、协议插入到各自对应的大数组中
    注意是从后往前加入的(为啥???)
    --------------

    // 取出当前类 cls 的 class_rw_t 数据
    auto rw = cls->data();

    // 存储方法、属性、协议数组到 rw 中【注意是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);
}
  • 先创建方法列表、属性列表、协议列表的新列表并且给它们分配内存,然后存储该cls所有的分类的方法、属性、协议,然后转交给了attachMethodLists方法(就是后面的那些写在一起了)

为什么需要准备方法列表这一步呢?

方法的查找算法是通过二分查找算法,说明sel-imp是有排序的,那么是如何排序的呢?

perpareMethodLists中主要调用了fixup方法 在 fixupMethodList 方法中会遍历 mlist,把 sel 中的名字跟地址设置到 meth,然后根据地址对 mlist 进行重新排序。

这也就意味着 remethodizeClass方法中实现类中方法(协议等)的序列化。

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

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

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

在这里插入图片描述

分类的实现部分说起来很简单,不过源码确实有点恶心

load方法和initialize方法

load方法

我们知道,在类和category中都可以有+load方法,那么有两个问题:

1)、在类的+load方法调用的时候,我们可以调用category中声明的方法么?

2)、这么些个+load方法,调用顺序是咋样的呢?

在这里插入图片描述 并且在每个类中的load方法都添加了load方法

当在build Phases的Compile Sources中设置编译顺序如下时: 在这里插入图片描述 结果是 在这里插入图片描述

更换1和2的顺序 在这里插入图片描述 在这里插入图片描述

再当我们把主类放到这两个分类的编译顺序下面,是不是还会按照编译顺序运行? 在这里插入图片描述 可以看到并不是 在这里插入图片描述

带上子类呢? 在这里插入图片描述

在这里插入图片描述

对于load方法:

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

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

initialize

不带子类

initialize是在类的方法第一次被调用时执行 覆盖类中的方法 只执行一次 分类2的test在前面,先调用分类2,所以分类2的initialize方法先执行 在这里插入图片描述

带上子类 在这里插入图片描述 调用子类的分类的test方法,可以看到创建了两遍父类的initialize方法 为啥呢??? 在这里插入图片描述

因为我在initialize方法中写了[super initialize],由于是系统自动调用,也不需要再调用 [super initialize] ,否则父类的initialize会被多次执行 在这里插入图片描述

假如写了[super initialize]这时,系统就会再回到父类,再打印一遍。所以会打印两遍 请添加图片描述

关联对象

虽然在分类中可以写@property添加属性,但是不会自动生成私有属性,也不会生成set,get方法的实现,只会生成set,get的声明,需要我们自己去实现。

具体怎么实现,就涉及到关联对象了 请添加图片描述 就像这样

我们为什么需要关联对象? 因为在分类中,@property并不会自动生成实例变量以及存取方法,所以一般使用关联对象为已经存在的类添加属性。 正如上面的图,我们需要objc_getAssociatedObjectobjc_setAssociatedObject两个方法帮我们实现在以及存在的类添加属性的问题

我们先来看这两个函数

基本使用

setAssociatedObject

objc_setAssociatedObject(self, personNameKey, personName, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

参数一: id object:给哪个对象添加属性,这里要给自己添加属性,使用self就行了 参数二:key:设置关联对象的key,根据key获取关联对象的属性的值,在objc_getAssociatedObject中通过次key获得属性的值并返回,相当于一个中间人 参数三:id value:关联的值,也就是set方法传入的值给属性去保存 参数四:policy:策略,属性以什么形式保存

有以下的几种:

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          指定相关的对象被复制,原子性
};

在这里插入图片描述

getAssociatedObject

 objc_getAssociatedObject(self, personNameKey);

两个参数 参数一:id object:获取哪个对象里面的关联的属性 参数二:key:什么属性,与objc_setAssociatedObject中的key相对应,即通过key值去除value

移除所有关联对象

- (void)removeAssociatedObjects {
    objc_removeAssociatedObjects(self);
}

可以看出关联对象的使用非常简单,接下来看看关联对象的底层原理

关联对象底层原理

核心技术有

  • AssociationsManager
  • AssociationsHashMap
  • ObjectAssociationMap
  • ObjcAssociation

直接开干

首先先分析一下这几个核心对象

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

其中只有一个变量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); }
    };

我们发现AssociationsHashMap继承自unordered_map

unordered_map中是以key_typemapped_type作为key和value

对应AssociationsHashMap内源码 disguised_ptr_t 是key ObjectAssociationMap *是value

接着我们继续来看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); }
    };

ObjectAssociationMap中同样以key、value的方式存储着ObjcAssociation

继续看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; }
    };

我们发现ObjcAssociation存储着policyvalue,这两个值正是我们调用objc_setAssociatedobject函数中传入的value和policy这两个值最终是存储在ObjcAssociation中的。 请添加图片描述

  • 关联对象并不是存储在被关联对象本身内存中
  • 关联对象存储在全局的统一的一个AssociationsManager
  • object作为key,一个被关联对象的所有关联对象都存储在同一个ObjectAssociationMap中
  • object被关联的对象不能为nil
  • 设置关联对象value为nil是,就相当于是移除关联对象

  • 这个结构也十分巧妙
    1. 一个objc对象不光有一个属性需要关联时,假设要关联nameage这两个属性,我们就以objc对象作为disguised_ptr_t,然后valueobjectAssociationMap这个字典类型,在这个字典类型中,分别使用@"name"@"age"作为key,传递进行的值和策略生成 objectAssociation作为value
    1. 如果有多个对象进行关联时,我们只需要在AssociationsHashMap中创造更多的键值对就可以解决这个问题
    1. 关联对象并不是存储在被关联对象本身内存中,而是存储在全局的统一的一个AssociationsManager

以及看完了这些核心函数,回到objc_setAssociatedObject

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

其内部调用了_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);
    id new_value = value ? acquireValue(value, policy) : nil;
    {
        // 初始化一个manager
        AssociationsManager manager;
        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 类型的迭代器
            AssociationsHashMap::iterator i = associations.find(disguised_object);
            if (i != associations.end()) {
                // secondary table exists
                // 获取到ObjectAssociationMap(key是外部传来的key,value是关联对象类ObjcAssociation)
                ObjectAssociationMap *refs = i->second;
                // ObjectAssociationMap::iterator 类型的迭代器
                ObjectAssociationMap::iterator j = refs->find(key);
                if (j != refs->end()) {
                    // 原来该key对应的有关联对象
                    // 将原关联对象的值存起来,并且赋新值
                    old_association = j->second;
                    j->second = ObjcAssociation(policy, new_value);
                } else {
                    // 无该key对应的关联对象,直接赋值即可
                    // ObjcAssociation(policy, new_value)提供了这样的构造函数
                    (*refs)[key] = ObjcAssociation(policy, new_value);
                }
            } else {
                // create the new association (first time).
                // 执行到这里,说明该对象是第一次添加关联对象
                // 初始化ObjectAssociationMap
                ObjectAssociationMap *refs = new ObjectAssociationMap;
                associations[disguised_object] = refs;
                // 赋值
                (*refs)[key] = ObjcAssociation(policy, new_value);
                // 设置该对象的有关联对象,调用的是setHasAssociatedObjects()方法
                object->setHasAssociatedObjects();
            }
        } else {
            // setting the association to nil breaks the association.
            // value无值,也就是释放一个key对应的关联对象
            AssociationsHashMap::iterator i = associations.find(disguised_object);
            if (i !=  associations.end()) {
                ObjectAssociationMap *refs = i->second;
                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);
}

首先根据我们传入的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;
}

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

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

之后我们看到传入的第一个参数object object经过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); }

DISGUISE函数其实仅仅对object做了位运算(取反?)


接着来看一下objc_getAssociatedObject函数

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

调用了_object_get_associative_reference这个方法

// 获取关联对象
id _object_get_associative_reference(id object, void *key) {
    id value = nil;
    uintptr_t policy = OBJC_ASSOCIATION_ASSIGN;
    {
        AssociationsManager manager;
        // 获取到manager中的AssociationsHashMap
        AssociationsHashMap &associations(manager.associations());
        // 获取对象的DISGUISE值
        disguised_ptr_t disguised_object = DISGUISE(object);
        AssociationsHashMap::iterator i = associations.find(disguised_object);
        if (i != associations.end()) {
            // 获取ObjectAssociationMap
            ObjectAssociationMap *refs = i->second;
            ObjectAssociationMap::iterator j = refs->find(key);
            if (j != refs->end()) {
                // 获取到关联对象ObjcAssociation
                ObjcAssociation &entry = j->second;
                // 获取到value
                value = entry.value();
                policy = entry.policy();
                if (policy & OBJC_ASSOCIATION_GETTER_RETAIN) {
                    objc_retain(value);
                }
            }
        }
    }
    if (value && (policy & OBJC_ASSOCIATION_GETTER_AUTORELEASE)) {
        objc_autorelease(value);
    }
    // 返回关联对像的值
    return value;
}

和set是一样的道理,获取到manager中的AssociatuinsHashMap,然后通过DISGUISE(object)获取到obj对应到AssociationsHashMap的key值。通过这个key值来通过迭代器去查找是否存在于AssociationsHashMap中。后面就是判空和取值的过程。

说完了set和get,下面就是remove的过程

// 移除对象object的所有关联对象
void objc_removeAssociatedObjects(id object) 
{
    if (object && object->hasAssociatedObjects()) {
        _object_remove_assocations(object);
    }
}

看一下_object_remove_assocations这个函数

// 移除对象object的所有关联对象
void _object_remove_assocations(id object) {
    // 声明了一个vector
    vector< ObjcAssociation,ObjcAllocator<ObjcAssociation> > elements;
    {
        AssociationsManager manager;
        AssociationsHashMap &associations(manager.associations());
        // 如果map size为空,直接返回
        if (associations.size() == 0) return;
        // 获取对象的DISGUISE值
        disguised_ptr_t disguised_object = DISGUISE(object);
        AssociationsHashMap::iterator i = associations.find(disguised_object);
        if (i != associations.end()) {
            // copy all of the associations that need to be removed.
            ObjectAssociationMap *refs = i->second;
            for (ObjectAssociationMap::iterator j = refs->begin(), end = refs->end(); j != end; ++j) {
                elements.push_back(j->second);
            }
            // remove the secondary table.
            delete refs;
            associations.erase(i);
        }
    }
    // the calls to releaseValue() happen outside of the lock.
    for_each(elements.begin(), elements.end(), ReleaseValue());
}

通过循环,将object对象向对应的所有关联对象全部删除。