Category分类
Category
是 比继承更为简洁 的方法来对Class
进行扩展,无需创建子类就可以为现有的类动态添加方法。
- 可以给项目内任何已经存在的类 添加
Category
- 甚至可以是系统库/闭源库等只暴露了声明文件的类 添加
Category
(看不到.m
文件的类) - 通过
Category
可以添加 实例方法、类方法、属性- 注意:通过
Category
可以添加属性,会声明setter
、getter
方法 ,但需要 开发者 自己 实现setter
、getter
方法(使用关联对象实现属性) - 通过
Class
添加属性,会默认生成并实现setter
、getter
方法
- 注意:通过
- 分类也可以把
framework
私有方法公开化- 比如我们假如我有一个类里有一个私有方法A 外界是调用不到的 但是我给这个类写了个
category
在里里面申明了一个方法(也叫A,只申明,不实现) 现在我import
这个category
调用 这个A 的情况是怎么样的呢?实际上这时候就会调用私有方法这个A,我们通过分类将私有方法公开化了
- 比如我们假如我有一个类里有一个私有方法A 外界是调用不到的 但是我给这个类写了个
- 通过
Category
可以 重新实现 在Class
中已存在的 方法 - 通过
Category
可以 重新实现 在 其它Category
中已存在/已实现 的方法
[ 在iOS
中,实例对象/类对象方法调用顺序严格依赖 源码文件的编译顺序,编译顺序的查看可以通过Xcode>Build Phases>Compile Sources
查看:
- 类与 各个分类 各自声明且实现各自的方法:没有方法的实现被覆盖,分类 只是扩展了 类的 功能
- 类与 各个分类 存在 声明 且实现 了同名的 方法: 存在 方法的实现被覆盖(实际上不是被覆盖,而是方法地址后挪,系统会找到同名方法在内存地址中位置较前的方法 实现 调用)
- 分类 方法实现 的优先级 > 原来的类
- 各个分类 中 被覆盖的情况严格 依赖 源码 文件的编译顺序:
- 先编译的 方法 会 先加入 方法列表「先入栈」
- 后编译的 方法 会 后加入 方法列表「后入栈」
- 系统在调用 方法 的实现的时候,通过 对象(实例对象、类对象等) 和 方法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总结
主要包含下面几种部分内容:
_method_list_t
类型的 对象方法列表结构体_method_list_t
类型的 类方法列表结构体_protocol_list_t
类型的 协议列表结构体_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");
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_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.addForClass
(load_images
中执行到这里时调用objc::unattachedCategories.addForClass
来将分类注册到它对应的主类里面去,方便后续重建类的方法列表,如果是map_images
中执行到这里时会调用attachCategories
。这两种情况调用分支不同的原因就是在load_images
中调用到的时候分类对应的主类并没有被实现,那些主类在load_images
后续获取分类的load
方法时才被实现,导致map_images
执行到那里的时候分类对应的主类已经被实现了,所以if就走了不同的分支) - (分类对应的主类是元类对象时)如果其有对应的主类,并其有类方法、协议,则调用
objc::unattachedCategories.addForClass
(load_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_images
中read_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)
得知,以cls
为key
,lc
为value
进行存储。
接着我们来看一下向主类中添加分类中内容的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方法
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
方法对其进行了最后分类的加载。
上面一大堆过程肯定看的迷迷糊糊,不妨按照下方总结的精简过程看上方的具体实现,会好看很多。
总结分类的加载
首先分类加载分为两种情况:非懒加载分类和懒加载分类,所以分类就有两条加载流程。
先讲非懒加载分类的加载流程:
- 进入
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
分支将分类和主类关联并将分类加载进了内存。
- 执行完
loadAllCategories
之后,我们回到load_images
中执行后面的内容,接着需要执行的是:prepare_load_methods
方法获取类和分类的load
方法,在其中调用了realizeClassWithoutSwift
方法来实现(初始化)分类所对应的主类,在其中又调用了methodizeClass
方法向主类中附加分类,在这之中又调用了objc::unattachedCategories.attachToClass
,在这里面又调用了attachCategories
来正式向主类中添加分类中的内容
(流程就是:prepare_load_methods –> realizeClassWithoutSwift –> methodizeClass –> objc::unattachedCategories.attachToClass –> attachCategories
)
此时的状态是: 分类对应的主类已经实现,并已经将分类中的内容添加到了主类当中
- 执行完
prepare_load_methods
之后,我们又回到load_images
中执行后面的内容,接着需要执行的是:call_load_methods
方法用来调用所有的类和分类的load
方法,让这些非懒加载的类和分类正式加载到程序中去。
(最后的流程就是调用了:call_load_methods
)
此时的状态是: 非懒加载类和其分类加载完毕
再讲懒加载分类的加载流程:
- 进入
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
分支将分类和主类关联并将分类加载进了内存。
- 循环执行完
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
)
此时的状态: 懒加载分类的主类已经得到了实现(初始化),且懒加载分类中的内容已经添加到了懒加载主类之中。
以上就是本人对于整个分类的加载的总结叙述,如有问题望大家指正。
– 未完