一、Category 简介
Category (分类) 是 Objective-C2.0 添加的语言特性,主要为已存在的类添加方法。Category 在既不子类化,也无需修改一个类的源码情况下,为原有的类添加方法,从而实现扩展一个类或者分离一个类。
虽然继承也能为已有的类添加新的方法和属性,但是继承关系增加了不必要的代码复杂度,在运行时候无法与父类的方法区分。
1. Category 作用
- 把类的不同实现方法分开到不同的文件里。这样做有几个显而易见的好处:
a) 可以减少单个文件的体积
b) 可以把不同的功能组织到不同的 Category 里
c) 可以由多个开发者共同完成一个类
d) 可以按需加载想要的 Category
e) ... - 声明私有方法。
- 模拟多继承。
- 将 framework 私有方法公开化。
2. Category(分类) 与 Extension(扩展) 区别
Extension 看起来很像一个匿名的 Category,但是 Extension 和有名字的category几乎完全是两个东西。 Extension 在编译期决议,它就是类的一部分,在编译期和头文件里的 @interface 以及实现文件里的 @implement 一起形成一个完整的类,它伴随类的产生而产生,亦随之一起消亡。Extension 一般用来隐藏类的私有信息,你必须有一个类的源码才能为一个类添加 Extension ,所以你无法为系统的类比如 NSString 添加 Extension。
但是 Category 则完全不一样,它是在运行期决议的。就 Category 和 Extension 的区别来看,我们可以推导出一个明显的事实,Extension 可以添加实例变量,而 Category 是无法添加实例变量的(因为在运行时,对象的内存布局已经确定,如果添加实例变量就会破坏类的内部布局,这对编译型语言来说是灾难性的)。
二、Category 的本质
我们知道,所有的 OC 类和对象(iOS底层原理 | OC对象的本质),在 runtime 层都是用 struct 结构体,category 也不例外,在 objc-runtime-new.h 中,Category 被定义为 category_t 结构体。category_t 结构体的数据结构如下:
typedef struct category_t *Category; struct category_t { const char *name; // 类名 classref_t cls; // 类,在运行时阶段通过 clasee_name(类名)对应到类对象 struct method_list_t *instanceMethods; // Category 中所有添加的对象方法列表 struct method_list_t *classMethods; // Category 中所有添加的类方法列表 struct protocol_list_t *protocols; // Category 中实现的所有协议列表 struct property_list_t *instanceProperties; // Category 中添加的所有属性 };
从 Category 的结构体定义中也可以看出,Category 可以为类添加对象方法、类方法、协议、属性。但是 Category 无法添加成员变量。
1. 透过 Category 的 C++ 源码观察 Category 的本质
想要了解 Category 的本质,我们需要通过 Category 的 C++ 源码进行分析。
以下是我们写的关于 Category 例子:
CategoryEssence.h
#import <Foundation/Foundation.h> NS_ASSUME_NONNULL_BEGIN @protocol CategoryEssenceProtocol <NSObject> - (void)categoryEssenceProtocolMethod; + (void)categoryEssenceProtocolClassMethod; @end @interface CategoryEssence : NSObject - (void)printName; + (void)printClassName; @end @interface CategoryEssence (Addition) <CategoryEssenceProtocol> - (void)printName; + (void)printClassName; @end NS_ASSUME_NONNULL_END
CategoryEssence.m
#import "CategoryEssence.h" @implementation CategoryEssence - (void)printName { NSLog(@"CategoryEssence printName"); } + (void)printClassName { NSLog(@"CategoryEssence printClassName"); } @end @implementation CategoryEssence(Addition) - (void)printName { NSLog(@"CategoryEssence(Addition) printName"); } + (void)printClassName { NSLog(@"CategoryEssence(Addition) printClassName"); } #pragma mark -- 协议方法 - (void)categoryEssenceProtocolMethod { NSLog(@"categoryEssenceProtocolMethod"); } + (void)categoryEssenceProtocolClassMethod { NSLog(@"categoryEssenceProtocolClassMethod"); } @end
将 CategoryEssence.m 通过
clang -rewrite-objc CategoryEssence.m
得到一个 4.5M、11W+ 的.cpp文件(在 iOS 工程下创建)。忽略掉无关代码,我们分析一波:
Category 的本质就是 _category_t 结构体 类型,其中包含了以下几部分:
- _method_list_t 类型的对象方法列表结构体;
- _method_list_t 类型的类方法列表结构体;
- _protocol_list_t 类型的协议列表结构体;
- _prop_list_t 类型的属性列表结构体;
但是 _category_t 不包含 _ivar_list_t 成员变量结构体,这间接说明 Category 不能添加成员变量的事实。
// @implementation CategoryEssence static void _I_CategoryEssence_printName(CategoryEssence * self, SEL _cmd) { NSLog((NSString *)&__NSConstantStringImpl__var_folders_3j_fvy599js5nnglvw7jslq09t80000gn_T_CategoryEssence_88f40a_mi_0); } static void _C_CategoryEssence_printClassName(Class self, SEL _cmd) { NSLog((NSString *)&__NSConstantStringImpl__var_folders_3j_fvy599js5nnglvw7jslq09t80000gn_T_CategoryEssence_88f40a_mi_1); } // @end // @implementation CategoryEssence(Addition) static void _I_CategoryEssence_Addition_printName(CategoryEssence * self, SEL _cmd) { NSLog((NSString *)&__NSConstantStringImpl__var_folders_3j_fvy599js5nnglvw7jslq09t80000gn_T_CategoryEssence_88f40a_mi_2); } static void _C_CategoryEssence_Addition_printClassName(Class self, SEL _cmd) { NSLog((NSString *)&__NSConstantStringImpl__var_folders_3j_fvy599js5nnglvw7jslq09t80000gn_T_CategoryEssence_88f40a_mi_3); } static void _I_CategoryEssence_Addition_categoryEssenceProtocolMethod(CategoryEssence * self, SEL _cmd) { NSLog((NSString *)&__NSConstantStringImpl__var_folders_3j_fvy599js5nnglvw7jslq09t80000gn_T_CategoryEssence_88f40a_mi_4); } static void _C_CategoryEssence_Addition_categoryEssenceProtocolClassMethod(Class self, SEL _cmd) { NSLog((NSString *)&__NSConstantStringImpl__var_folders_3j_fvy599js5nnglvw7jslq09t80000gn_T_CategoryEssence_88f40a_mi_5); } // @end #pragma warning(disable:4273) static struct /*_method_list_t*/ { unsigned int entsize; // sizeof(struct _objc_method) unsigned int method_count; struct _objc_method method_list[1]; } _OBJC_$_INSTANCE_METHODS_CategoryEssence __attribute__ ((used, section ("__DATA,__objc_const"))) = { sizeof(_objc_method), 1, {{(struct objc_selector *)"printName", "v16@0:8", (void *)_I_CategoryEssence_printName}} }; static struct /*_method_list_t*/ { unsigned int entsize; // sizeof(struct _objc_method) unsigned int method_count; struct _objc_method method_list[1]; } _OBJC_$_CLASS_METHODS_CategoryEssence __attribute__ ((used, section ("__DATA,__objc_const"))) = { sizeof(_objc_method), 1, {{(struct objc_selector *)"printClassName", "v16@0:8", (void *)_C_CategoryEssence_printClassName}} }; static struct _class_ro_t _OBJC_METACLASS_RO_$_CategoryEssence __attribute__ ((used, section ("__DATA,__objc_const"))) = { 1, sizeof(struct _class_t), sizeof(struct _class_t), (unsigned int)0, 0, "CategoryEssence", (const struct _method_list_t *)&_OBJC_$_CLASS_METHODS_CategoryEssence, 0, 0, 0, 0, }; static struct _class_ro_t _OBJC_CLASS_RO_$_CategoryEssence __attribute__ ((used, section ("__DATA,__objc_const"))) = { 0, sizeof(struct CategoryEssence_IMPL), sizeof(struct CategoryEssence_IMPL), (unsigned int)0, 0, "CategoryEssence", (const struct _method_list_t *)&_OBJC_$_INSTANCE_METHODS_CategoryEssence, 0, 0, 0, 0, }; extern "C" __declspec(dllimport) struct _class_t OBJC_METACLASS_$_NSObject; extern "C" __declspec(dllexport) struct _class_t OBJC_METACLASS_$_CategoryEssence __attribute__ ((used, section ("__DATA,__objc_data"))) = { 0, // &OBJC_METACLASS_$_NSObject, 0, // &OBJC_METACLASS_$_NSObject, 0, // (void *)&_objc_empty_cache, 0, // unused, was (void *)&_objc_empty_vtable, &_OBJC_METACLASS_RO_$_CategoryEssence, }; extern "C" __declspec(dllimport) struct _class_t OBJC_CLASS_$_NSObject; extern "C" __declspec(dllexport) struct _class_t OBJC_CLASS_$_CategoryEssence __attribute__ ((used, section ("__DATA,__objc_data"))) = { 0, // &OBJC_METACLASS_$_CategoryEssence, 0, // &OBJC_CLASS_$_NSObject, 0, // (void *)&_objc_empty_cache, 0, // unused, was (void *)&_objc_empty_vtable, &_OBJC_CLASS_RO_$_CategoryEssence, }; static void OBJC_CLASS_SETUP_$_CategoryEssence(void ) { OBJC_METACLASS_$_CategoryEssence.isa = &OBJC_METACLASS_$_NSObject; OBJC_METACLASS_$_CategoryEssence.superclass = &OBJC_METACLASS_$_NSObject; OBJC_METACLASS_$_CategoryEssence.cache = &_objc_empty_cache; OBJC_CLASS_$_CategoryEssence.isa = &OBJC_METACLASS_$_CategoryEssence; OBJC_CLASS_$_CategoryEssence.superclass = &OBJC_CLASS_$_NSObject; OBJC_CLASS_$_CategoryEssence.cache = &_objc_empty_cache; } #pragma section(".objc_inithooks$B", long, read, write) __declspec(allocate(".objc_inithooks$B")) static void *OBJC_CLASS_SETUP[] = { (void *)&OBJC_CLASS_SETUP_$_CategoryEssence, }; 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_CategoryEssence_$_Addition __attribute__ ((used, section ("__DATA,__objc_const"))) = { sizeof(_objc_method), 2, {{(struct objc_selector *)"printName", "v16@0:8", (void *)_I_CategoryEssence_Addition_printName}, {(struct objc_selector *)"categoryEssenceProtocolMethod", "v16@0:8", (void *)_I_CategoryEssence_Addition_categoryEssenceProtocolMethod}} }; 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_CategoryEssence_$_Addition __attribute__ ((used, section ("__DATA,__objc_const"))) = { sizeof(_objc_method), 2, {{(struct objc_selector *)"printClassName", "v16@0:8", (void *)_C_CategoryEssence_Addition_printClassName}, {(struct objc_selector *)"categoryEssenceProtocolClassMethod", "v16@0:8", (void *)_C_CategoryEssence_Addition_categoryEssenceProtocolClassMethod}} }; static struct /*_method_list_t*/ { unsigned int entsize; // sizeof(struct _objc_method) unsigned int method_count; struct _objc_method method_list[1]; } _OBJC_PROTOCOL_OPT_INSTANCE_METHODS_NSObject __attribute__ ((used, section ("__DATA,__objc_const"))) = { sizeof(_objc_method), 1, {{(struct objc_selector *)"debugDescription", "@16@0:8", 0}} }; static struct /*_prop_list_t*/ { unsigned int entsize; // sizeof(struct _prop_t) unsigned int count_of_properties; struct _prop_t prop_list[4]; } _OBJC_PROTOCOL_PROPERTIES_NSObject __attribute__ ((used, section ("__DATA,__objc_const"))) = { sizeof(_prop_t), 4, {{"hash","TQ,R"}, {"superclass","T#,R"}, {"description","T@\"NSString\",R,C"}, {"debugDescription","T@\"NSString\",R,C"}} }; struct _protocol_t _OBJC_PROTOCOL_NSObject __attribute__ ((used)) = { 0, "NSObject", 0, (const struct method_list_t *)&_OBJC_PROTOCOL_INSTANCE_METHODS_NSObject, 0, (const struct method_list_t *)&_OBJC_PROTOCOL_OPT_INSTANCE_METHODS_NSObject, 0, (const struct _prop_list_t *)&_OBJC_PROTOCOL_PROPERTIES_NSObject, sizeof(_protocol_t), 0, (const char **)&_OBJC_PROTOCOL_METHOD_TYPES_NSObject }; struct _protocol_t *_OBJC_LABEL_PROTOCOL_$_NSObject = &_OBJC_PROTOCOL_NSObject; static const char *_OBJC_PROTOCOL_METHOD_TYPES_CategoryEssenceProtocol [] __attribute__ ((used, section ("__DATA,__objc_const"))) = { "v16@0:8", "v16@0:8" }; static struct /*_protocol_list_t*/ { long protocol_count; // Note, this is 32/64 bit struct _protocol_t *super_protocols[1]; } _OBJC_PROTOCOL_REFS_CategoryEssenceProtocol __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_CategoryEssenceProtocol __attribute__ ((used, section ("__DATA,__objc_const"))) = { sizeof(_objc_method), 1, {{(struct objc_selector *)"categoryEssenceProtocolMethod", "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_CategoryEssenceProtocol __attribute__ ((used, section ("__DATA,__objc_const"))) = { sizeof(_objc_method), 1, {{(struct objc_selector *)"categoryEssenceProtocolClassMethod", "v16@0:8", 0}} }; struct _protocol_t _OBJC_PROTOCOL_CategoryEssenceProtocol __attribute__ ((used)) = { 0, "CategoryEssenceProtocol", (const struct _protocol_list_t *)&_OBJC_PROTOCOL_REFS_CategoryEssenceProtocol, (const struct method_list_t *)&_OBJC_PROTOCOL_INSTANCE_METHODS_CategoryEssenceProtocol, (const struct method_list_t *)&_OBJC_PROTOCOL_CLASS_METHODS_CategoryEssenceProtocol, 0, 0, 0, sizeof(_protocol_t), 0, (const char **)&_OBJC_PROTOCOL_METHOD_TYPES_CategoryEssenceProtocol }; struct _protocol_t *_OBJC_LABEL_PROTOCOL_$_CategoryEssenceProtocol = &_OBJC_PROTOCOL_CategoryEssenceProtocol; static struct /*_protocol_list_t*/ { long protocol_count; // Note, this is 32/64 bit struct _protocol_t *super_protocols[1]; } _OBJC_CATEGORY_PROTOCOLS_$_CategoryEssence_$_Addition __attribute__ ((used, section ("__DATA,__objc_const"))) = { 1, &_OBJC_PROTOCOL_CategoryEssenceProtocol }; extern "C" __declspec(dllexport) struct _class_t OBJC_CLASS_$_CategoryEssence; static struct _category_t _OBJC_$_CATEGORY_CategoryEssence_$_Addition __attribute__ ((used, section ("__DATA,__objc_const"))) = { "CategoryEssence", 0, // &OBJC_CLASS_$_CategoryEssence, (const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_CategoryEssence_$_Addition, (const struct _method_list_t *)&_OBJC_$_CATEGORY_CLASS_METHODS_CategoryEssence_$_Addition, (const struct _protocol_list_t *)&_OBJC_CATEGORY_PROTOCOLS_$_CategoryEssence_$_Addition, 0, }; static void OBJC_CATEGORY_SETUP_$_CategoryEssence_$_Addition(void ) { _OBJC_$_CATEGORY_CategoryEssence_$_Addition.cls = &OBJC_CLASS_$_CategoryEssence; } #pragma section(".objc_inithooks$B", long, read, write) __declspec(allocate(".objc_inithooks$B")) static void *OBJC_CATEGORY_SETUP[] = { (void *)&OBJC_CATEGORY_SETUP_$_CategoryEssence_$_Addition, }; static struct _class_t *L_OBJC_LABEL_CLASS_$ [1] __attribute__((used, section ("__DATA, __objc_classlist,regular,no_dead_strip")))= { &OBJC_CLASS_$_CategoryEssence, }; static struct _category_t *L_OBJC_LABEL_CATEGORY_$ [1] __attribute__((used, section ("__DATA, __objc_catlist,regular,no_dead_strip")))= { &_OBJC_$_CATEGORY_CategoryEssence_$_Addition, }; static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };
二、Category 的加载方式
我们知道,Objective-C 的运行是依赖 Objective-C 的 Runtime 的,而 Objective-C 的 runtime 和其他系统库一样,是 OS X 和 iOS 通过 dyld 动态加载的。
1. dyld 加载大致流程
dyld(the dynamic link editor) 的相关代码可在苹果开源网站上进行下载:dyld 苹果开源代码
关于 dyld 的详解可移步到 dyld详解
dyld 大致加载步骤:
- 配置环境变量;
- 加载共享缓存;
- 初始化主 APP;
- 插入动态缓存库;
- 链接主程序;
- 链接插入的动态库;
- 初始化主程序:OC, C++ 全局变量初始化;
- 返回主程序入口函数。
2. Category 的加载过程
Runtime 是在在第七步开始初始化的,所以 Category 也在这一步做加载。
我们先看一在主程序初始化时候的调用栈:
dyldbootstrap::start ---> dyld::_main ---> initializeMainExecutable ---> runInitializers ---> recursiveInitialization ---> doInitialization ---> doModInitFunctions ---> _objc_init
最后调用的 _objc_init 是 libobjc 库中的方法, 是 Runtime 的初始化过程,也是 Objective-C 的入口。
在 _objc_init 这一步中:Runtime 向 dyld 绑定了回调,当 image 加载到内存后,dyld 会通知 Runtime 进行处理,Runtime 接手后调用 map_images 做解析和处理,调用 _read_images 方法把 Category 的对象方法、协议、属性添加到类上,把 Category(分类) 的类方法、协议添加到类的 MetaClass 上;接下来 load_images 中调用 call_load_methods 方法,遍历所有加载进来的 Class,按继承层级和编译顺序依次调用 Class 的 load 方法和其 Category 的 load 方法。
加载 Category 的调用栈:
_objc_init ---> map_images ---> map_images_nolock ---> _read_images(加载分类) ---> load_images
关于 Category 的加载过程,在这里我就浅尝即止,主要就了解一下加载过程,有兴趣可以去下载官方源码进行详细阅读分析。
三、Category 和 Class 的 +load 方法
Category 中的的方法、属性、协议附加到类上的操作,是在 + load 方法执行之前进行的。也就是说,在 + load 方法执行之前,类中就已经加载了 Category 中的的方法、属性、协议。
而 Category 和 Class 的 + load 方法的调用顺序规则如下所示:
- 先调用主类,按照编译顺序,顺序地根据继承关系由父类向子类调用;
- 调用完主类,再调用分类,按照编译顺序,依次调用;
- + load 方法除非主动调用,否则只会调用一次。
通过这样的调用规则,我们可以知道:主类的 + load 方法调用一定在分类 + load 方法调用之前。但是分类 + load 方法调用顺序并不是按照继承关系调用的,而是依照编译顺序确定的,这也导致了 + load 方法的调用顺序并不一定确定。一个顺序可能是:
父类 -> 子类 -> 父类类别 -> 子类类别
也可能是
父类 -> 子类 -> 子类类别 -> 父类类别