• 首页 首页 icon
  • 工具库 工具库 icon
    • IP查询 IP查询 icon
  • 内容库 内容库 icon
    • 快讯库 快讯库 icon
    • 精品库 精品库 icon
    • 问答库 问答库 icon
  • 更多 更多 icon
    • 服务条款 服务条款 icon

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

武飞扬头像
西邮郭富城
帮助3

一、分类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);
};
学新通

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

2.分类结构的源码解析

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

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

@interface TestNSObject (TestCategoryNSObject)<TestCategoryProtocol>

@property (nonatomic, copy) NSString *personName;

- (void)TestCategoryMethod;
  (void)TestCategoryClassMethod;

@end

该分类遵守的协议:

@protocol TestCategoryProtocol <NSObject>

- (void)protocolMethod;

  (void)protocolClassMethod;

@end

并实现其中的方法:

@implementation TestNSObject (TestCategoryNSObject)

- (void)TestCategoryMethod {
    NSLog(@"TestCategoryMethod");
}

  (void)TestCategoryClassMethod {
    NSLog(@"TestCategoryClassMethod");
}

- (void)protocolMethod {
    NSLog(@"protocolMethod");
}

  (void)protocolClassMethod {
    NSLog(@"protocolClassMethod");
}

@end
学新通

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,
};
学新通

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

  • 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}}
};
学新通
  • - (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}}
};
学新通
  • (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]; // 因为我们在定义该协议的时候,有一个<NSObject>的操作,所以这里就会将其加进来,类似于继承吧,所以就有一个数据
} _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 //刚才打包封装的协议的结构体
};
学新通
  • 协议的封装逻辑还是挺清晰的,先是初始化赋值一个协议结构体,然后把这个结构体封装打包,然后再把打包的结构体赋给该分类中的协议列表。这样就算该分类中有多个协议,那么我们将其协议结构体初始化后,打包给该分类的协议列表就可以了。

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"}} // 两个数据,一个属性名,一个属性类型及其修饰符
};
学新通
  • 从该源码可知,我们在分类中定义的属性,它会有一个属性列表来储存,但是分类中并没有成员变量结构体(_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
}
学新通

其调用_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
    }
}
学新通

然后在其中调用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.获取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;
}
学新通
  • 通过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);
    }
}
学新通

该方法只是得到该类的分类列表,然后将其给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);
}
学新通
  • 该方法主要就是将我们分类中的信息添加到本类的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])); // 将新数据插在前面
    }
}
学新通

一图说明这个方法是在干什么:
学新通

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

// 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中
*/
学新通

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

  • 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

学新通

五、关联对象

上面也说了,分类中不可以添加实例变量,但是可以添加属性,但是属性不能自动生成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);

参数一:给哪个对象添加属性,如果要给自己添加属性,使用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 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);

参数一:获取哪个对象里面的关联的属性
参数二:获取的是那个属性,字典的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);

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

举例:

我们给上面我们说的分类中的@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;
    }
}

我们通过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;
    }
};
学新通

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

我们发现这个玩意是继承自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); }
};

我们发现这个也是一个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; }
};

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

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

  • 关联对象并不是存储在被关联对象本身内存中
  • 关联对象存储在全局的统一的一个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);
}

它调用了_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);
}
学新通

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

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

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

3.2 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
        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;
}
学新通

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

3.3 objc_removeAssociatedObjects

// 移除对象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
        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());
}
学新通

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

这篇好文章是转载于:学新通技术网

  • 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
  • 本站站名: 学新通技术网
  • 本文地址: /boutique/detail/tanhfhjfkb
系列文章
更多 icon
同类精品
更多 icon
继续加载