目录:

  • 参考的博客:
  • 前言
    • 一、Category分类
  • Extension扩展
  • Category的实质
    • Category结构体
    • 将分类转成C++看起
      • 对象方法列表结构体
      • 类方法列表结构体
      • 协议列表结构体
      • 属性列表结构体
      • category总结
    • 分类在运行期做了什么
      • _objc_init
      • _read_images
      • load_images
      • loadAllCategories
      • load_categories_nolock
      • 调用load方法准备 (prepare_load_methods)
      • 调用load方法
      • 总结分类的加载
  • 关联对象
    • 基本使用
      • setAssociatedObject
      • getAssociatedObject
      • 移除所有关联对象
    • 关联对象底层原理

参考的博客:

[iOS开发]Category、Extension和关联对象
01-iOS-OC常用语法-----简介与常规应用|Category、Extension、关联对象、Protocol
[iOS面试粮食]OC语言—Category(分类)和类扩展(extension)、关联对象
iOS八股文(九)启动流程 -objc源码解析objc_init、map_images、load_images
第十八节—类扩展与关联对象
iOS底层探索之map_images
iOS 分类的加载原理
第十七节—load_images
dyld中的objc_init、map_images、load_images

前言

OC中的面向对象的三大特性是:封装、继承、多态,此外还提供了其他对OC类进行(功能/‘成员变量’)扩展的方式:

  • Category
  • Extension
  • Protocol
  • 关联对象

一、Category分类

Category 是 比继承更为简洁 的方法来对Class进行扩展,无需创建子类就可以为现有的类动态添加方法

Category 是 比继承更为简洁 的方法来对Class进行扩展,无需创建子类就可以为现有的类动态添加方法。

  • 可以给项目内任何已经存在的类 添加 Category
  • 甚至可以是系统库/闭源库等只暴露了声明文件的类 添加 Category (看不到.m 文件的类)
  • 通过 Category 可以添加 实例方法、类方法、属性
    • 注意:通过 Category 可以添加属性,会声明setter、getter方法 ,但需要 开发者 自己 实现 setter、getter方法(使用关联对象实现属性)
    • 通过 Class 添加属性,会默认生成并实现 setter、getter方法
  • 分类也可以把framework私有方法公开化
    • 比如我们假如我有一个类里有一个私有方法A 外界是调用不到的 但是我给这个类写了个category 在里里面申明了一个方法(也叫A,只申明,不实现) 现在我import这个category 调用 这个A 的情况是怎么样的呢?实际上这时候就会调用私有方法这个A,我们通过分类将私有方法公开化了
  • 通过 Category 可以 重新实现 在 Class中已存在的 方法
  • 通过 Category 可以 重新实现 在 其它 Category中已存在/已实现 的方法
  • iOS中,实例对象/类对象方法调用顺序严格依赖 源码文件的编译顺序,编译顺序的查看可以通过Xcode>Build Phases>Compile Sources查看:

    • 1.各个分类 各自声明且实现各自的方法:没有方法的实现被覆盖,分类 只是扩展了 类的 功能
    • 2.各个分类 存在 声明 且实现 了同名的 方法: 存在 方法的实现被覆盖(实际上不是被覆盖,而是方法地址后挪,系统会找到同名方法在内存地址中位置较前的方法 实现 调用)
      • 分类 方法实现 的优先级 > 原来的类
      • 各个分类 中 被覆盖的情况严格 依赖 源码 文件的编译顺序:
        • 先编译的 方法 会 先加入 方法列表「先入栈」
        • 后编译的 方法 会 后加入 方法列表「后入栈」
        • 系统在调用 方法 的实现的时候,通过 对象(实例对象、类对象等) 和 方法API 在底层发送消息,拿到方法 实现 的 实现 IMP指针 找到 方法的具体实现(实际上最终拿到的方法实现,是后编译的源码文件中的方法实现)

官方介绍的优点有两点:

  • 可以把类的实现分开在几个不同的文件里面

    • 可以减少分开文件的体积
    • 可以把不同的功能组织到不同的category
    • 可以有多个开发者共同完成一个类
    • 可以按需加载想要的类别等等
  • 声明专有方法

Extension扩展

延展Extension)可以理解成是匿名的Category

  • 可以用来给类 添加 属性和方法 的声明,不作用在Subclass
  • 可以 通过 在.m文件 给其它类 或者 当前 类 添加 Extension
    • 给当前类添加 Extension 时,编译器会默认给 添加的属性 声明且实现 其setter&&getter方法
  • 也可以 通过 .h文件 给类 添加 Extension
  • 要对 Extension添加的 属性和方法进行实现
  • 若 对 Extension的实现中 ,重新实现 原来 类或其它分类中已有的方法,不会对原来的方法执行产生影响(因为没有自身的.m文件,不存在源码实现文件的编译的情况)

Extension的作用更多在于拆分结构复杂的类,比较清晰的暴露声明

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++文件来了解一下:

我们首先先创建一个分类:

#import "Car.h"
#import "protocolForCar.h"NS_ASSUME_NONNULL_BEGIN@interface Car (match)<CarProtocol>
@property (nonatomic, copy) NSString *carType;- (void)matchPrint;
+ (void)matchClass;@endNS_ASSUME_NONNULL_END

我们在其中声明了一个实例方法、一个类方法、一个属性
分类遵循一个协议,协议里面也是一个类方法和对象方法

#ifndef protocolForCar_h
#define protocolForCar_h@protocol CarProtocol <NSObject>- (void)protocolMethod;
+ (void)protocolClassMethod;@end

然后使用:xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc Car+match.m -o test.cpp,将该分类的.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_Car_$_match __attribute__ ((used, section ("__DATA,__objc_const"))) =
{"Car",0, // &OBJC_CLASS_$_Car,(const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_Car_$_match,(const struct _method_list_t *)&_OBJC_$_CATEGORY_CLASS_METHODS_Car_$_match,(const struct _protocol_list_t *)&_OBJC_CATEGORY_PROTOCOLS_$_Car_$_match,(const struct _prop_list_t *)&_OBJC_$_PROP_LIST_Car_$_match,
};//结构体数组
static struct _category_t *L_OBJC_LABEL_CATEGORY_$ [1] __attribute__((used, section ("__DATA, __objc_catlist,regular,no_dead_strip")))= {&_OBJC_$_CATEGORY_Car_$_match,
};
  • 我们可以看到重点的三个元素

    • category结构体
    • category结构体的赋值语句
    • category结构体数组

对象方法列表结构体

//本类对象方法的实现
static void _I_Car_match_matchPrint(Car * self, SEL _cmd) {NSLog((NSString *)&__NSConstantStringImpl__var_folders_6p_mn3hwpz14_7dg_gr79rtm4n80000gn_T_Car_match_633f34_mi_0);
}//协议中对象方法的实现
static void _I_Car_match_protocolMethod(Car * self, SEL _cmd) {NSLog((NSString *)&__NSConstantStringImpl__var_folders_6p_mn3hwpz14_7dg_gr79rtm4n80000gn_T_Car_match_633f34_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_Car_$_match __attribute__ ((used, section ("__DATA,__objc_const"))) = {sizeof(_objc_method),2,{{(struct objc_selector *)"matchPrint", "v16@0:8", (void *)_I_Car_match_matchPrint},{(struct objc_selector *)"protocolMethod", "v16@0:8", (void *)_I_Car_match_protocolMethod}}
};
  • - (void)matchPrint- (void)protocolMethod方法的实现
  • 对象方法结构体列表结构体

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

类方法列表结构体

//本类类方法的实现
static void _C_Car_match_matchClass(Class self, SEL _cmd) {NSLog((NSString *)&__NSConstantStringImpl__var_folders_6p_mn3hwpz14_7dg_gr79rtm4n80000gn_T_Car_match_633f34_mi_1);
}//协议中的类方法
static void _C_Car_match_protocolClassMethod(Class self, SEL _cmd) {NSLog((NSString *)&__NSConstantStringImpl__var_folders_6p_mn3hwpz14_7dg_gr79rtm4n80000gn_T_Car_match_633f34_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_Car_$_match __attribute__ ((used, section ("__DATA,__objc_const"))) = {sizeof(_objc_method),2,{{(struct objc_selector *)"matchClass", "v16@0:8", (void *)_C_Car_match_matchClass},{(struct objc_selector *)"protocolClassMethod", "v16@0:8", (void *)_C_Car_match_protocolClassMethod}}
};
  • + (void)matchClass+ (void)protocolClassMethod类方法的实现
  • 类方法列表结构体

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

协议列表结构体

//协议结构体
struct _protocol_t {void * isa;  // NULLconst char *protocol_name;const struct _protocol_list_t * protocol_list; // super protocolsconst 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;  // = 0const char ** extendedMethodTypes;
};//分类中添加的协议列表结构体
static struct /*_protocol_list_t*/ {long protocol_count;  // Note, this is 32/64 bitstruct _protocol_t *super_protocols[1];
} _OBJC_PROTOCOL_REFS_CarProtocol __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_CarProtocol __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_CarProtocol __attribute__ ((used, section ("__DATA,__objc_const"))) = {sizeof(_objc_method),1,{{(struct objc_selector *)"protocolClassMethod", "v16@0:8", 0}}
};//结构体赋值
struct _protocol_t _OBJC_PROTOCOL_CarProtocol __attribute__ ((used)) = {0,"CarProtocol",(const struct _protocol_list_t *)&_OBJC_PROTOCOL_REFS_CarProtocol,(const struct method_list_t *)&_OBJC_PROTOCOL_INSTANCE_METHODS_CarProtocol,(const struct method_list_t *)&_OBJC_PROTOCOL_CLASS_METHODS_CarProtocol,0,0,0,sizeof(_protocol_t),0,(const char **)&_OBJC_PROTOCOL_METHOD_TYPES_CarProtocol
};
struct _protocol_t *_OBJC_LABEL_PROTOCOL_$_CarProtocol = &_OBJC_PROTOCOL_CarProtocol;
  • 协议列表结构体
  • 协议列表 对象方法列表结构体
  • 协议列表 类方法列表结构体
  • 结构体赋值语句

属性列表结构体

//属性结构体
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_Car_$_match __attribute__ ((used, section ("__DATA,__objc_const"))) = {sizeof(_prop_t),1,{{"carType","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();//runtime准备,创建2张表runtime_init();//异常初始化exception_init();
#if __OBJC2__//缓存cache_t::init();
#endif//macos专有_imp_implementationWithBlock_init();_dyld_objc_notify_register(&map_images, load_images, unmap_image);#if __OBJC2__didCallDyldNotifyRegister = true;
#endif
}

其中我们发现了一些初始化创建过程,这里我们主要关注一下runtime_init

void runtime_init(void)
{//分类加载表objc::unattachedCategories.init(32);//类的加载表objc::allocatedClasses.init();
}

可以看到其中有一张分类加载表。

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

_read_images

    // Discover categories. Only do this after the initial category// attachment has been done. For categories present at startup,// discovery is deferred until the first load_images call after// the call to _dyld_objc_notify_register completes. rdar://problem/53119145//发现类别。只有在初始类别之后才这样做//附件已完成。对于启动时出现的类别,//发现延迟到之后的第一个load_images调用//调用_dyld_objc_notify_register完成。rdar: / /问题/ 53119145//意思是非懒加载的分类走的是load_images//那么作为对应,懒加载的分类就走的是这里 _read_images中的操作//全局变量didInitialAttachCategories,执行load_images 的时候设置为YES//所以只有当执行过load_images的时候,这里才会遍历load_catagories_nolock去加载分类,而这里遍历的也是一些懒加载的类的分类。//这里的判断条件didInitialAttachCategories意思是是否进行完初始的分类添加(如果进行过的话,也就是非懒加载的分类以经添加了的话,就进去执行if分支中的内容)if (didInitialAttachCategories) {for (EACH_HEADER) {load_categories_nolock(hi);}}ts.log("IMAGE TIMES: discover categories");

这里需要注意:didInitialAttachCategories只会在load_images中设置为true,也就是说只有执行过load_images将非懒加载分类添加到主类之后,这里才会遍历load_categories_nolock

我们再来到load_images方法,发现里面调用了loadAllCategories函数,再到loadAllCategories函数中找到load_categories_nolock

load_images

load_images方法源码如下:

void
load_images(const char *path __unused, const struct mach_header *mh)
{if (!didInitialAttachCategories && didCallDyldNotifyRegister) {didInitialAttachCategories = true;// 加载所有的分类loadAllCategories();}// Return without taking locks if there are no +load methods here.//有load方法的话直接返回,没有的话才执行后面找load方法和调用load方法的代码//如果这里没有+load方法,返回时不带锁。if (!hasLoadMethods((const headerType *)mh)) return;recursive_mutex_locker_t lock(loadMethodLock);// Discover load methods{// 找到load方法mutex_locker_t lock2(runtimeLock);prepare_load_methods((const headerType *)mh);}// Call +load methods (without runtimeLock - re-entrant)//调用load方法call_load_methods();
}

这里我们先介绍一下load方法的概念:

  • load方法:当类加载到运行环境中的时候就会调用且仅调用一次,同时注意一个类只会加载一次(类加载有别于引用类,可以这么说,所有类都会在程序启动的时候加载一次,不管有没有在目前显示的视图类中引用到,这个涉及到了App的启动流程)

然后我们放上一张load_images的流程图,便于理解其中的整个过程,想要了解load_images的详细内容可参考:第十七节—load_images:

其中有一个重要的点,就是在获取分类的load方法的时候,我们是先获取了非懒加载分类的列表,然后调用realizeClassWithoutSwift对其分类的主类进行了实现,这点非常重要。后续整个流程总结时会提到,调用realizeClassWithoutSwift的源码如下:

//prepare_load_methods是load_images中获取主类和分类load方法时调用的函数
void prepare_load_methods(const headerType *mhdr)
{size_t count, i;runtimeLock.assertLocked();classref_t const *classlist = _getObjc2NonlazyClassList(mhdr, &count);for (i = 0; i < count; i++) {schedule_class_load(remapClass(classlist[i]));}category_t * const *categorylist = _getObjc2NonlazyCategoryList(mhdr, &count);for (i = 0; i < count; i++) {category_t *cat = categorylist[i];Class cls = remapClass(cat->cls);if (!cls) continue;  // category for ignored weak-linked classif (cls->isSwiftStable()) {_objc_fatal("Swift class extensions and categories on Swift ""classes are not allowed to have +load methods");}//此处调用realizeClassWithoutSwift实现了分类对应的主类realizeClassWithoutSwift(cls, nil);ASSERT(cls->ISA()->isRealized());add_category_to_loadable_list(cat);}
}

用于实现主类的方法:realizeClassWithoutSwift的源码如下:

static Class realizeClassWithoutSwift(Class cls, Class previously)
{runtimeLock.assertLocked();class_rw_t *rw; // 读写数据Class supercls; // 父类Class metacls;  // 元类if (!cls) return nil; // 如果为空,返回nilif (cls->isRealized()) return cls;  // 如果已经实现,直接返回ASSERT(cls == remapClass(cls));// fixme verify class is not in an un-dlopened part of the shared cache?auto ro = (const class_ro_t *)cls->data(); // 读取类的数据auto isMeta = ro->flags & RO_META; // 是否是元类if (ro->flags & RO_FUTURE) { // rw已经有值的话走这里// This was a future class. rw data is already allocated.rw = cls->data();ro = cls->data()->ro();ASSERT(!isMeta);cls->changeInfo(RW_REALIZED|RW_REALIZING, RW_FUTURE);} else { // 正常的类走这里// Normal class. Allocate writeable class data.rw = objc::zalloc<class_rw_t>(); // 开辟rwrw->set_ro(ro); // 把cls的数据ro赋值给rwrw->flags = RW_REALIZED|RW_REALIZING|isMeta; // 更新flagscls->setData(rw); // 再把rw设置为cls的data数据}#if FAST_CACHE_METAif (isMeta) cls->cache.setBit(FAST_CACHE_META);
#endif// Choose an index for this class.// Sets cls->instancesRequireRawIsa if indexes no more indexes are availablecls->chooseClassArrayIndex();if (PrintConnecting) {_objc_inform("CLASS: realizing class '%s'%s %p %p #%u %s%s",cls->nameForLogging(), isMeta ? " (meta)" : "", (void*)cls, ro, cls->classArrayIndex(),cls->isSwiftStable() ? "(swift)" : "",cls->isSwiftLegacy() ? "(pre-stable swift)" : "");}// Realize superclass and metaclass, if they aren't already.//实现超类和元类(如果尚未实现)。// This needs to be done after RW_REALIZED is set above, for root classes.//对于根类,需要在上面设置了RW_REALIZED之后执行此操作。// This needs to be done after class index is chosen, for root metaclasses.//对于根元类,需要在选择类索引之后执行此操作。// This assumes that none of those classes have Swift contents,//   or that Swift's initializers have already been called.//   fixme that assumption will be wrong if we add support//   for ObjC subclasses of Swift classes.// 递归调用 realizeClassWithoutSwift ,实现父类和元类supercls = realizeClassWithoutSwift(remapClass(cls->superclass), nil);metacls = realizeClassWithoutSwift(remapClass(cls->ISA()), nil);#if SUPPORT_NONPOINTER_ISAif (isMeta) { // 如果是元类,对isa处理// Metaclasses do not need any features from non pointer ISA// This allows for a faspath for classes in objc_retain/objc_release.cls->setInstancesRequireRawIsa();} else { // 不是元类,也是对isa处理// Disable non-pointer isa for some classes and/or platforms.// Set instancesRequireRawIsa.bool instancesRequireRawIsa = cls->instancesRequireRawIsa();bool rawIsaIsInherited = false;static bool hackedDispatch = false;if (DisableNonpointerIsa) {// Non-pointer isa disabled by environment or app SDK versioninstancesRequireRawIsa = true;}else if (!hackedDispatch  &&  0 == strcmp(ro->name, "OS_object")){// hack for libdispatch et al - isa also acts as vtable pointerhackedDispatch = true;instancesRequireRawIsa = true;}else if (supercls  &&  supercls->superclass  &&supercls->instancesRequireRawIsa()){// This is also propagated by addSubclass()// but nonpointer isa setup needs it earlier.// Special case: instancesRequireRawIsa does not propagate// from root class to root metaclassinstancesRequireRawIsa = true;rawIsaIsInherited = true;}if (instancesRequireRawIsa) {cls->setInstancesRequireRawIsaRecursively(rawIsaIsInherited);}}
// SUPPORT_NONPOINTER_ISA
#endif// Update superclass and metaclass in case of remapping// 确定继承链,赋值父类和元类cls->superclass = supercls;cls->initClassIsa(metacls);// Reconcile instance variable offsets / layout.// 协调实例变量的偏移量/布局。// This may reallocate class_ro_t, updating our ro variable.if (supercls  &&  !isMeta) reconcileInstanceVariables(cls, supercls, ro);// Set fastInstanceSize if it wasn't set already.// 经过上一步,再次协调属性对齐后,设置实例大小cls->setInstanceSize(ro->instanceSize);// Copy some flags from ro to rw// 赋值一些 ro 中的 flags标识位 到 rwif (ro->flags & RO_HAS_CXX_STRUCTORS) {cls->setHasCxxDtor();if (! (ro->flags & RO_HAS_CXX_DTOR_ONLY)) {cls->setHasCxxCtor();}}// Propagate the associated objects forbidden flag from ro or from// the superclass.// 从ro或父类传播关联的对象禁止标志。if ((ro->flags & RO_FORBIDS_ASSOCIATED_OBJECTS) ||(supercls && supercls->forbidsAssociatedObjects())){rw->flags |= RW_FORBIDS_ASSOCIATED_OBJECTS;}// Connect this class to its superclass's subclass lists// 添加当前类到父类的子类列表中,如果没有父类,设置自己就是根类if (supercls) {addSubclass(supercls, cls);} else {addRootClass(cls);}// Attach categories// 附加分类methodizeClass(cls, previously);return cls;
}

可以看到最后也是执行了一个methodizeClass函数来向主类附加分类,methodizeClass源码如下:

//methodizeClass函数用于附加分类
static void methodizeClass(Class cls, Class previously)
{runtimeLock.assertLocked();bool isMeta = cls->isMetaClass();auto rw = cls->data();auto ro = rw->ro(); // 读取ro数据auto rwe = rw->ext(); // 读取ext,赋值给rwe// Methodizing for the first timeif (PrintConnecting) {_objc_inform("CLASS: methodizing class '%s' %s", cls->nameForLogging(), isMeta ? "(meta)" : "");}// Install methods and properties that the class implements itself.method_list_t *list = ro->baseMethods(); // 获取ro中的方法列表if (list) {// 对方法列表list重新排序prepareMethodLists(cls, &list, 1, YES, isBundleClass(cls));if (rwe) rwe->methods.attachLists(&list, 1); // 如果有rwe,添加方法列表list到rwe的methodsList}property_list_t *proplist = ro->baseProperties;if (rwe && proplist) {rwe->properties.attachLists(&proplist, 1);}protocol_list_t *protolist = ro->baseProtocols;if (rwe && protolist) {rwe->protocols.attachLists(&protolist, 1);}// Root classes get bonus method implementations if they don't have // them already. These apply before category replacements.if (cls->isRootMetaclass()) {// root metaclass 根元类添加initialize方法addMethod(cls, @selector(initialize), (IMP)&objc_noop_imp, "", NO);}// Attach categories. 附加分类if (previously) {if (isMeta) {objc::unattachedCategories.attachToClass(cls, previously,ATTACH_METACLASS);} else {// When a class relocates, categories with class methods// may be registered on the class itself rather than on// the metaclass. Tell attachToClass to look for those.objc::unattachedCategories.attachToClass(cls, previously,ATTACH_CLASS_AND_METACLASS);}}objc::unattachedCategories.attachToClass(cls, cls,isMeta ? ATTACH_METACLASS : ATTACH_CLASS);#if DEBUG// Debug: sanity-check all SELs; log method list contentsfor (const auto& meth : rw->methods()) {if (PrintConnecting) {_objc_inform("METHOD %c[%s %s]", isMeta ? '+' : '-', cls->nameForLogging(), sel_getName(meth.name));}ASSERT(sel_registerName(sel_getName(meth.name)) == meth.name); }
#endif
}

还有我们发现load_images里面的确调用了loadAllCategories函数,接着我们再来看一下loadAllCategories的实现:

loadAllCategories

static void loadAllCategories() {mutex_locker_t lock(runtimeLock);for (auto *hi = FirstHeader; hi != NULL; hi = hi->getNext()) {//调用load_categories_nolock来加载分类load_categories_nolock(hi);}
}

接着我们来看load_categories_nolock函数的实现(这个方法在load_images中和map_images两个流程中都会有调用到,其中执行的内容各稍有不同):

load_categories_nolock

static void load_categories_nolock(header_info *hi) {bool hasClassProperties = hi->info()->hasCategoryClassProperties();size_t count;auto processCatlist = [&](category_t * const *catlist) {for (unsigned i = 0; i < count; i++) {category_t *cat = catlist[i];Class cls = remapClass(cat->cls);locstamped_category_t lc{cat, hi};if (!cls) {// Category's target class is missing (probably weak-linked).// Ignore the category.if (PrintConnecting) {_objc_inform("CLASS: IGNORING category \?\?\?(%s) %p with ""missing weak-linked target class",cat->name, cat);}continue;}// Process this category.if (cls->isStubClass()) {// Stub classes are never realized. Stub classes// don't know their metaclass until they're// initialized, so we have to add categories with// class methods or properties to the stub itself.// methodizeClass() will find them and add them to// the metaclass as appropriate.if (cat->instanceMethods ||cat->protocols ||cat->instanceProperties ||cat->classMethods ||cat->protocols ||(hasClassProperties && cat->_classProperties)){objc::unattachedCategories.addForClass(lc, cls);}} else {// First, register the category with its target class.// Then, rebuild the class's method lists (etc) if// the class is realized.//首先,将类别注册到它的目标类。//如果那个类已经实现就重建它的方法列表if (cat->instanceMethods ||  cat->protocols||  cat->instanceProperties){if (cls->isRealized()) {//一般是map_images中调用load_categories_nolock函数时cls都会实现的,所以会走这个方法去将分类中的内容粘贴到主类中,但是现版本中map_images的注释中说到将分类添加主类的操作延迟到了第一次load_images执行时attachCategories(cls, &lc, 1, ATTACH_EXISTING);//而如果是load_images中调用的load_categories_nolock函数的话,一般cls都没实现,就会走下面的else里的方法,将分类添加到unattachedCategories(未向主类粘贴内容的分类表)表中} else {//这个表就是分类表objc::unattachedCategories.addForClass(lc, cls);}}if (cat->classMethods  ||  cat->protocols||  (hasClassProperties && cat->_classProperties)){if (cls->ISA()->isRealized()) {//一般是map_images中调用load_categories_nolock函数时元类也都会实现的,所以会走这个方法去将分类中的内容粘贴到主元类中attachCategories(cls->ISA(), &lc, 1, ATTACH_EXISTING | ATTACH_METACLASS);//而如果是load_images中调用的load_categories_nolock函数的话,一般其元类都没实现,就会走下面的else里的方法,将分类添加到unattachedCategories(未向主元类类粘贴内容的分类表)表中} else {// runtime_init的时候创建,第一部分有讲到objc::unattachedCategories.addForClass(lc, cls->ISA());}}}}};processCatlist(hi->catlist(&count));processCatlist(hi->catlist2(&count));
}
  • 获取category列表list
  • 遍历category list中的每一个category
  • 获取category的对应的主类cls,如果没有cls就跳过(continue)这个继续获取下一个
  • 分类对应的主类是类对象时)如果其有对应的主类,并其有实例方法、协议、属性,则调用objc::unattachedCategories.addForClassload_images中执行到这里时调用objc::unattachedCategories.addForClass来将分类注册到它对应的主类里面去,方便后续重建类的方法列表,如果是map_images中执行到这里时会调用attachCategories。这两种情况调用分支不同的原因就是在load_images中调用到的时候分类对应的主类并没有被实现,那些主类在load_images后续获取分类的load方法时才被实现,导致map_images执行到那里的时候分类对应的主类已经被实现了,所以if就走了不同的分支)
  • 分类对应的主类是元类对象时)如果其有对应的主类,并其有类方法、协议,则调用objc::unattachedCategories.addForClassload_images中执行到这里时调用objc::unattachedCategories.addForClass来将分类注册到它对应的主类里面去,方便后续重建类的方法列表,如果是map_images中执行到这里时会调用attachCategories

这里肯定会疑惑为什么load_images中先将分类添加到unattachedCategories中,再将实现主类将分类内容添加到主类,map_images中最后调用的read_images中也是先将分类添加到unattachedCategories中,再将实现主类将分类内容添加到主类,其实从map_images处理分类那段代码的上方注释就可以理解,我们的load_images中走的添加分类到主类处理的都是非懒加载的分类,而read_images中走的添加分类到主类处理的都是懒加载的分类,而且都是清一色的先将分类添加到unattachedCategories中与主类产生关联并存放分类到内存中,然后再等到后面调用realizeClassWithoutSwift函数实现(初始化)主类的时候,再调用methodizeClass实现的具体的分类内容添加到主类

简言之就是加载分类有两个路径,一个是处理非懒加载分类load_images中的路径,一个是处理懒加载分类map_imagesread_images处理的路径。

整个map_images的流程大致如下图:

有趣的是,上面的第10步,初始化懒加载类,实际上也是调用我们上方说的realizeClassWithoutSwift进行的。

另外,对于懒加载类非懒加载类的区别是:当前类是否实现 load 方法,实现了load方法就是非懒加载类,反之亦然,还有,懒加载类的数据加载推迟到第一次接收到消息的时候才开始加载,非懒加载类map_images执行中就加载了所有类的数据

然后我们言归正传,回到load_categories_nolock函数上,其整个函数的流程其实是将所有分类添加到runtime_init中初始化的unattachedCategories表中或者调用attachCategories直接向主类中添加分类中的内容,注意这里的unattachedCategories表,意思是未将分类内容粘贴到主类的那些分类的表,说明后面就需要进行attachCategories操作来向主类粘贴。

然后我们来看一下unattachedCategories表:

class UnattachedCategories : public ExplicitInitDenseMap<Class, category_list>
{public://将分类和主类关联起来void addForClass(locstamped_category_t lc, Class cls){runtimeLock.assertLocked();if (slowpath(PrintConnecting)) {_objc_inform("CLASS: found category %c%s(%s)",cls->isMetaClassMaybeUnrealized() ? '+' : '-',cls->nameForLogging(), lc.cat->name);}auto result = get().try_emplace(cls, lc);if (!result.second) {result.first->second.append(lc);}}//这个是向本类粘贴分类内容的方法void attachToClass(Class cls, Class previously, int flags){runtimeLock.assertLocked();ASSERT((flags & ATTACH_CLASS) ||(flags & ATTACH_METACLASS) ||(flags & ATTACH_CLASS_AND_METACLASS));auto &map = get();auto it = map.find(previously);if (it != map.end()) {category_list &list = it->second;if (flags & ATTACH_CLASS_AND_METACLASS) {int otherFlags = flags & ~ATTACH_CLASS_AND_METACLASS;//可以看到调用了attachCategories加载分类attachCategories(cls, list.array(), list.count(), otherFlags | ATTACH_CLASS);attachCategories(cls->ISA(), list.array(), list.count(), otherFlags | ATTACH_METACLASS);} else {attachCategories(cls, list.array(), list.count(), flags);}map.erase(it);}}void eraseCategoryForClass(category_t *cat, Class cls){runtimeLock.assertLocked();auto &map = get();auto it = map.find(cls);if (it != map.end()) {category_list &list = it->second;list.erase(cat);if (list.count() == 0) {map.erase(it);}}}void eraseClass(Class cls){runtimeLock.assertLocked();get().erase(cls);}
};

其中的addForClass其实就是将分类加载到内存的,里面我们发现有一个try_emplace方法,其代码如下:

  template <typename... Ts>std::pair<iterator, bool> try_emplace(const KeyT &Key, Ts &&... Args) {BucketT *TheBucket;if (LookupBucketFor(Key, TheBucket))return std::make_pair(makeIterator(TheBucket, getBucketsEnd(), true),false); // Already in map.// Otherwise, insert the new element.TheBucket = InsertIntoBucket(TheBucket, Key, std::forward<Ts>(Args)...);return std::make_pair(makeIterator(TheBucket, getBucketsEnd(), true),true);}

这个创建一个存储桶的结构(这是一个键值对形式的结构),向里面存内容,结合上面的函数调用get().try_emplace(cls, lc)得知,以clskeylcvalue进行存储。

接着我们来看一下向主类中添加分类中内容的attachCategories函数的源码:

// Attach method lists and properties and protocols from categories to a class.
// Assumes the categories in cats are all loaded and sorted by load order,
// oldest categories first.
//将方法列表、属性和协议从类别附加到一个类。
//假设猫的类别都是加载的,并按加载顺序排序,
//最古老的类别先开始。
static void
attachCategories(Class cls, const locstamped_category_t *cats_list, uint32_t cats_count,int flags)
{if (slowpath(PrintReplacedMethods)) {printReplacements(cls, cats_list, cats_count);}if (slowpath(PrintConnecting)) {_objc_inform("CLASS: attaching %d categories to%s class '%s'%s",cats_count, (flags & ATTACH_EXISTING) ? " existing" : "",cls->nameForLogging(), (flags & ATTACH_METACLASS) ? " (meta)" : "");}/** Only a few classes have more than 64 categories during launch.* This uses a little stack, and avoids malloc.** Categories must be added in the proper order, which is back* to front. To do that with the chunking, we iterate cats_list* from front to back, build up the local buffers backwards,* and call attachLists on the chunks. attachLists prepends the* lists, so the final result is in the expected order.*//**只有少数类在启动时拥有超过64个类别。*这使用了一个小堆栈,并避免了malloc。**类别必须以正确的顺序添加,这是回来*前面。为了使用分块实现这一点,我们需要迭代cats_list*从前面到后面,向后建立本地缓冲区,并在区块上调用attachLists。attachLists突出显示的*列表,因此最终结果按照预期的顺序。* ///创建方法列表、属性列表、协议列表,用来存储分类的方法、属性、协议constexpr uint32_t ATTACH_BUFSIZ = 64;method_list_t   *mlists[ATTACH_BUFSIZ];property_list_t *proplists[ATTACH_BUFSIZ];protocol_list_t *protolists[ATTACH_BUFSIZ];uint32_t mcount = 0;// 记录方法的数量uint32_t propcount = 0;// 记录属性的数量uint32_t protocount = 0;// 记录协议的数量bool fromBundle = NO;// 记录是否是从 bundle 中取的bool isMeta = (flags & ATTACH_METACLASS);//取出当前类 cls 的 class_rwe_t 数据auto rwe = cls->data()->extAllocIfNeeded();//遍历分类for (uint32_t i = 0; i < cats_count; i++) {auto& entry = cats_list[i];// 取出分类中的方法列表。如果是元类,取得的是类方法列表;否则取得的是对象方法列表method_list_t *mlist = entry.cat->methodsForMeta(isMeta);if (mlist) {if (mcount == ATTACH_BUFSIZ) {prepareMethodLists(cls, mlists, mcount, NO, fromBundle, __func__);rwe->methods.attachLists(mlists, mcount);mcount = 0;}mlists[ATTACH_BUFSIZ - ++mcount] = mlist;// 将方法列表放入 mlists 方法列表数组中fromBundle |= entry.hi->isBundle();// 分类的头部信息中存储了是否是 bundle,将其记住}// 取出分类中的属性列表,如果是元类,取得的是 nilproperty_list_t *proplist =entry.cat->propertiesForMeta(isMeta, entry.hi);if (proplist) {if (propcount == ATTACH_BUFSIZ) {rwe->properties.attachLists(proplists, propcount);propcount = 0;}proplists[ATTACH_BUFSIZ - ++propcount] = proplist;}// 取出分类中遵循的协议列表protocol_list_t *protolist = entry.cat->protocolsForMeta(isMeta);if (protolist) {if (protocount == ATTACH_BUFSIZ) {rwe->protocols.attachLists(protolists, protocount);protocount = 0;}protolists[ATTACH_BUFSIZ - ++protocount] = protolist;}}if (mcount > 0) {// 存储方法、属性、协议数组到 rwe 中// 准备方法列表 mlists 中的方法【为什么需要准备方法列表这一步?】prepareMethodLists(cls, mlists + ATTACH_BUFSIZ - mcount, mcount,NO, fromBundle, __func__);// 将新方法列表添加到 rwe 中的方法列表中rwe->methods.attachLists(mlists + ATTACH_BUFSIZ - mcount, mcount);if (flags & ATTACH_EXISTING) {// 清除 cls 的缓存列表flushCaches(cls, __func__, [](Class c){// constant caches have been dealt with in prepareMethodLists// if the class still is constant here, it's fine to keepreturn !c->cache.isConstantOptimizedCache();});}}// 将新属性列表添加到 rwe 中的属性列表中rwe->properties.attachLists(proplists + ATTACH_BUFSIZ - propcount, propcount);// 将新协议列表添加到 rwe 中的协议列表中rwe->protocols.attachLists(protolists + ATTACH_BUFSIZ - protocount, protocount);
}
  • 先创建方法列表、属性列表、协议列表的新列表并且给它们分配内存,然后存储该cls所有的分类的方法、属性、协议,然后转交给了attachLists方法(就是后面的那几行代码)

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

方法的查找算法是通过二分查找算法,说明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;array_t *newArray = (array_t *)malloc(array_t::byteSize(newCount));newArray->count = newCount;array()->count = newCount;for (int i = oldCount - 1; i >= 0; i--)newArray->lists[i + addedCount] = array()->lists[i];for (unsigned i = 0; i < addedCount; i++)newArray->lists[i] = addedLists[i];free(array());setArray(newArray);validate();}//大数组中原本没有小数组,再到前面添加一个小数组else if (!list  &&  addedCount == 1) {// 0 lists -> 1 listlist = addedLists[0];validate();} //大数组中原本有一个小数组,再到前面添加多个小数组else {// 1 list -> many listsPtr<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;for (unsigned i = 0; i < addedCount; i++)array()->lists[i] = addedLists[i];validate();}}

具体的保证新添加的数组在大数组前面的实现上方代码已经体现地十分清晰了,就是对数组元素的简单插入,先将原来的元素后移我们需要新添加的元素的数量,然后将需要新添加的元素从下标0开始依次插入就实现了新添加的在前面。

下图很生动地表现了上述三种插入情况:

然后我们又回到load_images,可以看到其中还调用了prepare_load_methods函数和call_load_methods函数,一个是用来 找到 所有非懒加载类非懒加载分类load方法的函数,一个是调用load方法进行最后 加载类和分类 的函数。

接下来我们先来看一下prepare_load_methods的实现:

调用load方法准备 (prepare_load_methods)

void prepare_load_methods(const headerType *mhdr)
{size_t count, i;\
runtimeLock.assertLocked();//获取所有非懒加载类
classref_t const *classlist = _getObjc2NonlazyClassList(mhdr, &count);
for (i = 0; i < count; i++) {schedule_class_load(remapClass(classlist[i]));
}
//获取所有非懒加载分类
category_t * const *categorylist = _getObjc2NonlazyCategoryList(mhdr, &count);
for (i = 0; i < count; i++) {category_t *cat = categorylist[i];Class cls = remapClass(cat->cls);if (!cls) continue;  // category for ignored weak-linked class//swift没有load方法if (cls->isSwiftStable()) {_objc_fatal("Swift class extensions and categories on Swift ""classes are not allowed to have +load methods");}//实现类realizeClassWithoutSwift(cls, nil);ASSERT(cls->ISA()->isRealized());add_category_to_loadable_list(cat);
}

从所有的非懒加载类、非懒加载分类中找出load方法。

调用load方法

void call_load_methods(void)
{static bool loading = NO;bool more_categories;//加锁:线程安全loadMethodLock.assertLocked();// Re-entrant calls do nothing; the outermost call will finish the job.if (loading) return;loading = YES;void *pool = objc_autoreleasePoolPush();do {// 1. Repeatedly call class +loads until there aren't any morewhile (loadable_classes_used > 0) {call_class_loads();}// 2. Call category +loads ONCE  加载分类more_categories = call_category_loads();// 3. Run more +loads if there are classes OR more untried categories} while (loadable_classes_used > 0  ||  more_categories);objc_autoreleasePoolPop(pool);loading = NO;
}

可以是调用了call_category_loads();函数里面调用了分类的load方法对其进行了最后分类的加载。

上面一大堆过程肯定看的迷迷糊糊,不妨按照下方总结的精简过程看上方的具体实现,会好看很多。

总结分类的加载

首先分类加载分为两种情况:非懒加载分类懒加载分类,所以分类就有两条加载流程。

先讲非懒加载分类的加载流程:

  1. 进入load_images,执行loadAllCategories,在loadAllCategories中调用了load_categories_nolock,再到load_categories_nolock中调用了addForClass
    流程就是:load_images –> loadAllCategories –> load_categories_nolock –> objc::unattachedCategories.addForClass

此时的状态是: 分类对应的主类都还没有实现(没有被初始化),我们只是调用了load_categories_nolock中的objc::unattachedCategories.addForClass分支将分类和主类关联并将分类加载进了内存。

  1. 执行完loadAllCategories之后,我们回到load_images中执行后面的内容,接着需要执行的是:prepare_load_methods方法获取类和分类的load方法,在其中调用了realizeClassWithoutSwift方法来实现(初始化)分类所对应的主类,在其中又调用了methodizeClass方法向主类中附加分类,在这之中又调用了objc::unattachedCategories.attachToClass,在这里面又调用了attachCategories来正式向主类中添加分类中的内容
    流程就是: prepare_load_methods –> realizeClassWithoutSwift –> methodizeClass –> objc::unattachedCategories.attachToClass –> attachCategories

此时的状态是: 分类对应的主类已经实现,并已经将分类中的内容添加到了主类当中

  1. 执行完prepare_load_methods之后,我们又回到load_images中执行后面的内容,接着需要执行的是:call_load_methods方法用来调用所有的类和分类的load方法,让这些非懒加载的类和分类正式加载到程序中去。
    最后的流程就是调用了: call_load_methods

此时的状态是: 非懒加载类和其分类加载完毕

再讲懒加载分类的加载流程:

  1. 进入map_images,执行map_images_nolock,再执行其中的_read_images_read_images执行到与分类相关的部分是一个判断,判断是否执行过一次load_images,如果执行过load_images的话那个判断的参数didInitialAttachCategories的值就会是YES,然后就可以执行那个if中的代码,那些代码是循环调用load_categories_nolock,对于懒加载的分类,它们对应的懒加载主类还没有实现,所以又会调用load_categories_nolock中的objc::unattachedCategories.addForClass分支将分类和主类关联并将分类加载进了内存
    流程就是: map_images –> map_images_nolock –> _read_images –> load_categories_nolock –> objc::unattachedCategories.addForClass

此时的状态: 懒加载分类对应的主类还没有实现(初始化),我们只是调用了load_categories_nolock中的objc::unattachedCategories.addForClass分支将分类和主类关联并将分类加载进了内存。

  1. 循环执行完load_categories_nolock之后,我们又回到_read_images之中,接下来需要执行的是非懒加载类的实现(初始化),我们会调用到realizeClassWithoutSwift方法,不过由于我们在load_images当中已经调用过realizeClassWithoutSwift方法并实现了非懒加载类,所以这次刚刚进入realizeClassWithoutSwift就会返回nil而不执行任何操作。接着我们继续在_read_images中执行后续的代码,我们现在需要执行的是对懒加载类的实现(初始化),我们依然调用的是realizeClassWithoutSwift对懒加载类进行实现,并在其中调用了methodizeClass方法向主类中附加分类,在这之中又调用了objc::unattachedCategories.attachToClass,在这里面又调用了attachCategories来正式向主类中添加分类中的内容
    (流程就是:realizeClassWithoutSwift –> realizeClassWithoutSwift –> methodizeClass –> objc::unattachedCategories.attachToClass –> attachCategories

此时的状态: 懒加载分类的主类已经得到了实现(初始化),且懒加载分类中的内容已经添加到了懒加载主类之中。

以上就是本人对于整个分类的加载的总结叙述,如有问题望大家指正。

关联对象

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

具体怎么实现,就涉及到关联对象了:

#import "MainClass+addClass.h"
#import "objc/runtime.h"@implementation MainClass (addClass)static char *stringTestKey = "stringTestKey";//setter方法
- (void) setStringTest:(NSString *)string {//关联对象的set方法,这几个参数分别是:源对象,关键字,关联的对象和一个关联策略(关联策略就类似于是属性关键字)objc_setAssociatedObject(self, stringTestKey, string, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}//getter方法
- (NSString *) stringTest {//关联对象的get方法,用我们之前set时用的关键字来获取id stringTest = objc_getAssociatedObject(self, stringTestKey);if (stringTest) {return stringTest;} else {return nil;}
}//尝试打印我们的关联对象
- (void) print {//进行属性的赋值操作,会调用我们实现的setter方法self.stringTest = @"对关联对象赋值!";//打印属性的值NSLog(@"the stringTest = %@", self.stringTest);
}@end

就像上方代码一样去设置关联对象

为什么需要关联对象?
因为在分类中,@property并不会自动生成实例变量以及存取方法,所以一般使用关联对象为已经存在的类添加属性。

正如上面的图,我们需要objc_getAssociatedObjectobjc_setAssociatedObject两个方法帮我们实现在以及存在的类添加属性的问题

我们先来看这两个函数

基本使用

setAssociatedObject

//关联对象的set方法,这几个参数分别是:源对象,关键字,关联的对象和一个关联策略(关联策略就类似于是属性关键字)
objc_setAssociatedObject(self, Key, parameterName, 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, Key);

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

移除所有关联对象

- (void)removeAssociatedObjects {//下方代码是移除self中所有的关联对象objc_removeAssociatedObjects(self);
}

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

关联对象底层原理

核心技术有:

  • AssociationsManager
  • AssociationsHashMap
  • ObjectAssociationMap
  • ObjcAssociation

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

class AssociationsManager {// associative references: object pointer -> PtrPtrHashMap.// AssociationsManager中只有一个变量AssociationsHashMapstatic 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作为keyvalue

对应AssociationsHashMap内源码
disguised_ptr_tkey
ObjectAssociationMap *value

接着我们继续来看ObjectAssociationMap:

 // ObjectAssociationMap是字典,key是从外面传过来的key,例如@selector(hello),value是关联对象,也就是ObjcAssociationclass 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和nilObjcAssociation() : _policy(0), _value(nil) {}uintptr_t policy() const { return _policy; }id value() const { return _value; }bool hasValue() { return _value != nil; }};

我们发现ObjcAssociation存储着policy、value,这两个值正是我们调用objc_setAssociatedobject函数中传入的valuepolicy这两个值最终是存储在ObjcAssociation中的

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

可以看到这个结构非常巧妙:

  1. 一个objc对象可能有多个属性需要关联,假设我们现在要关联sexname这两个属性,我们就以objc对象作为disguised_ptr_t,然后valueobjectAssociationMap这个字典类型,在这个字典类型中,分别使用"sex""name"作为key,传递我们设置的值和策略生成objcAssociation对象作为value
  2. 如果有多个对象进行关联时,我们只需要在AssociationsHashMap中创造更多的键值对就可以解决这个问题。
  3. 关联对象并不是存储在被关联对象本身内存中,而是存储在全局的统一的一个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,也是全局唯一的一个managerAssociationsManager manager;AssociationsHashMap &associations(manager.associations());// 获取对象的DISGUISE值,作为AssociationsHashMap的keydisguised_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).// 执行到这里,说明该对象是第一次添加关联对象// 初始化ObjectAssociationMapObjectAssociationMap *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经过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`函数:

// 获取关联对象的方法
// 关联对象的get方法,用我们之前set时用的关键字来获取
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中的AssociationsHashMapAssociationsHashMap &associations(manager.associations());// 获取对象的DISGUISE值disguised_ptr_t disguised_object = DISGUISE(object);AssociationsHashMap::iterator i = associations.find(disguised_object);if (i != associations.end()) {// 获取ObjectAssociationMapObjectAssociationMap *refs = i->second;ObjectAssociationMap::iterator j = refs->find(key);if (j != refs->end()) {// 获取到关联对象ObjcAssociationObjcAssociation &entry = j->second;// 获取到valuevalue = 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对应到AssociationsHashMapkey值。通过这个key值来通过迭代器去查找是否存在于AssociationsHashMap中。后面就是判空和取值的过程。

讲完了setget,下面就是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) {// 声明了一个vectorvector< 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) {//遍历过程中将每个需要删除的ObjectAssociationMap中的value都保存进elements容器elements.push_back(j->second);}// remove the secondary table.//移除object所对应的ObjectAssociationMapdelete refs;associations.erase(i);}}// the calls to releaseValue() happen outside of the lock.//将elements容器中保存的刚才需要删除的value进行遍历,边遍历边releasefor_each(elements.begin(), elements.end(), ReleaseValue());
}

可以看到具体实现是通过循环,将object对象向对应的所有关联对象全部删除(即就是循环遍历object对象所对应的AssociationsHashMap中的全部信息并将那些信息添加到elements容器中,最后再将object对象所对应的AssociationsHashMap删除掉,这两步完成之后再将刚才记录的需要删除的object对象所对应的AssociationsHashMap中的全部信息的value值全部进行一个release操作)。

[iOS]-Category、Extension和关联对象相关推荐

  1. 【iOS开发】——Category底层原理、Extension、关联对象

    [iOS开发]--Category底层原理.Extension.关联对象 Category是什么?它可以用来干什么? Category特点 Category的实质以及实现过程 Category结构体 ...

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

    文章目录 什么是Category? 分类和扩展 Category的实质 Category结构体 从C++开始看起 对象方法列表结构体 类方法列表结构体 协议列表结构体 属性列表结构体 category ...

  3. iOS Runtime特性之关联对象

    前言 现在你准备用一个系统的类或者是你写的类,但是这个类并不能满足你的需求,你需要额外添加一个属性. 一般解决办法要么是extends(继承),要么使用category(类别). 而我并不推荐使用ex ...

  4. iOS Category 添加属性实现原理 - 关联对象

    iOS Category 添加属性实现原理 - 关联对象 RunTime为Category动态关联对象 使用RunTime给系统的类添加属性,首先需要了解对象与属性的关系.对象一开始初始化的时候其属性 ...

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

    目录 一.分类Category和扩展Extension 1.分类Category(运行期) 2.扩展Extension(编译期) 3.分类和扩展的区别 二.分类Category的实质 1.分类的结构 ...

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

    分类.扩展和关联对象 一.分类与扩展的区别 1.`category`类别(分类) 2.`extension`(类扩展) 二.关联对象的实现 1.关联对象的实现步骤: 2.关联对象分析: 三.关联对象- ...

  7. oc 协议 回调 静态成员_OC底层原理探究:Category、关联对象和block本质

    1.分类Category的使用 // 给MJPerson类添加分类 @interface MJPerson : NSObject - (void)run; @end@implementation MJ ...

  8. 【iOS高级资深工程师面试篇】②、2022年,金九银十我为你准备了《iOS高级资深工程师面试知识总结》 Objective-C语言特性部分1/2 分类-关联对象-扩展-代理

    iOS高级资深工程师面试篇系列 - 已更新3篇 UI部分1/3 -UITableView-事件传递&视图响应 UI部分2/3 -图像显示原理-UI卡顿&掉帧 UI部分3/3 -UIVi ...

  9. 【iOS】—— 分类,扩展和关联对象

    分类,扩展和关联对象 文章目录 分类,扩展和关联对象 分类和扩展 分类概念 扩展概念 两者区别 分类的实质 关联对象 通过关联对象给分类添加属性 关联对象的用处 关联对象的API 给声明的属性添加se ...

最新文章

  1. 详解|清华大学100页PPT:工业机器人技术详解
  2. BCH区块链上的预言机项目——Oracles
  3. [POI2015]CZA
  4. Android之基于xmpp openfire smack开发之openfire介绍和部署[1]
  5. javascript基本数据类型与值类型引用类型说明
  6. python设计模式3-抽象工厂模式
  7. 计算机多了一个虚拟硬盘,移动硬盘中安装多个虚拟操作系统 -电脑资料
  8. 按钮控制android progressbar,Android ProgressBar手动控制开始和停止
  9. paip.设置自定义404不起作用解决.txt
  10. python画正切函数_Python
  11. CodeSmith模板
  12. 德尔菲法 Delphi 专家判断
  13. Kali渗透-ARP断网攻击与监听
  14. hive查看表中列的信息命令_Linux查看硬件信息之dmidecode命令详解
  15. python编译 pyd 工具_windows平台 python生成 pyd文件
  16. 原生js实现3D轮播图
  17. web开发要学习什么技术,HTML实体字符列表
  18. Swift 基础 枚举详解(代码)
  19. 小程序开发API之获取系统信息wx.getSystemInfo()、wx.getSystemInfoSync()
  20. 大端小端与LSB和MSB的小故事

热门文章

  1. Java基础语法 (Java Doc)
  2. 死亡细胞读取外部存档文件
  3. 测试题:64 匹马,8 个赛道,最少多少次比赛找出最快的 4 匹马?
  4. javaweb网上订餐系统
  5. Python年利率计算器【N日年化收益率】
  6. 几道web前端面试题
  7. 【DCDC转换器】BUCK电路的演进
  8. Echarts之圆饼图用法
  9. Gaea学习--Gaea是什么?
  10. Vivado18.3-Zynq PS的开发流程(Hello World) 学习笔记