在Objective-C 2.0中,提供了Category
这个语言特性,可以动态地为已有类添加新行为。那么我们在使用Category
时,有没有想过 Category
是什么?它是如何实现的?我们今天就来研究下Category
的底层实现!
1、Category
的本质
我们全局搜索category
可以找到它的声明与定义:
//Category 本质是一个结构指针,指向结构体实例 category_t
typedef struct category_t *Category;;
// 结构 category_t
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;//分类添加的实例属性列表
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);
};
从Category
结构成员看出可以在 Category
中添加实例方法、类方法,实现某些协议,添加属性;但是不可以在 Category
中添加实例变量。
那么 Objective-C 是如何实现 Category
中添加的实例方法、类方法,实现的协议,添加的属性呢?
2、Category
的源码转换过程
2.1、 Objective-C 源码
在程序某处声明的一个协议ObjectDelegate
:
@protocol ObjectDelegate <NSObject>
- (void)logObjectInfo;
@end
使用 Objective-C 写一段 Category
的源码:
@interface SuperModel (Test)
<ObjectDelegate>
@property (nonatomic ,strong) NSString *nickName;
@property (nonatomic ,strong) NSString *age;
- (void)logNickName;
+ (void)logHello;
@end
@implementation SuperModel (Test)
- (void)logNickName{
NSLog(@"class_getName ==== %s",class_getName(self.class));
}
+ (void)logHello{
NSLog(@"class_getName ---- %s",class_getName(self.class));
}
- (void)setNickName:(NSString *)nickName{
objc_setAssociatedObject(self, @selector(nickName), nickName, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (NSString *)nickName{
return objc_getAssociatedObject(self, _cmd);
}
#pragma mark - ObjectDelegate
- (void)logObjectInfo{
NSLog(@"实现协议 ObjectDelegate 的 -logObjectInfo 方法");
}
@end
2.2、转为 C++ 源码
xcrun -sdk iphonesimulator clang -rewrite-objc main.m
使用上述指令将上文的 Objective-C 代码转为 C++ 代码,可以生成一个 .cpp
文件,该文件有几万行代码,很多代码并不是本节我们研究的方向所以暂不关注!在该文件搜索关键字 Test
查找与分类有关的实现代码:
2.2.1、一些方法的实现
在 .cpp
文件找到关于 Category
中添加的实例方法、类方法的关键代码:
//实例方法 - (void)logNickName 的实现函数
static void _I_SuperModel_Test_logNickName(SuperModel * self, SEL _cmd) {
NSLog((NSString *)&__NSConstantStringImpl__var_folders_6___xgbfnx57zz3j5cdqx19dtnm0000gn_T_Model_ff5446_mi_15,class_getName(((Class (*)(id, SEL))(void *)objc_msgSend)((id)self, sel_registerName("class"))));
}
//类方法 + (void)logHello 的实现函数
static void _C_SuperModel_Test_logHello(Class self, SEL _cmd) {
NSLog((NSString *)&__NSConstantStringImpl__var_folders_6___xgbfnx57zz3j5cdqx19dtnm0000gn_T_Model_ff5446_mi_16,class_getName(((Class (*)(id, SEL))(void *)objc_msgSend)((id)self, sel_registerName("class"))));
}
//属性 nickName 的 set 函数
static void _I_SuperModel_Test_setNickName_(SuperModel * self, SEL _cmd, NSString *nickName) {
objc_setAssociatedObject(self, sel_registerName("nickName"), nickName, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
//属性 nickName 的 get 函数
static NSString * _I_SuperModel_Test_nickName(SuperModel * self, SEL _cmd) {
return objc_getAssociatedObject(self, _cmd);
}
//实现协议的函数
static void _I_SuperModel_Test_logObjectInfo(SuperModel * self, SEL _cmd) {
NSLog((NSString *)&__NSConstantStringImpl__var_folders_6___xgbfnx57zz3j5cdqx19dtnm0000gn_T_Model_ff5446_mi_17);
}
//分类的实例方法列表
static struct /*_method_list_t*/ {
unsigned int entsize; // sizeof(struct _objc_method)
unsigned int method_count;
struct _objc_method method_list[4];
} _OBJC_$_CATEGORY_INSTANCE_METHODS_SuperModel_$_Test __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_objc_method),
4,
{{(struct objc_selector *)"logNickName", "v16@0:8", (void *)_I_SuperModel_Test_logNickName},
{(struct objc_selector *)"setNickName:", "v24@0:8@16", (void *)_I_SuperModel_Test_setNickName_},
{(struct objc_selector *)"nickName", "@16@0:8", (void *)_I_SuperModel_Test_nickName},
{(struct objc_selector *)"logObjectInfo", "v16@0:8", (void *)_I_SuperModel_Test_logObjectInfo}}
};
//分类的类方法列表
static struct /*_method_list_t*/ {
unsigned int entsize; // sizeof(struct _objc_method)
unsigned int method_count;
struct _objc_method method_list[1];
} _OBJC_$_CATEGORY_CLASS_METHODS_SuperModel_$_Test __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_objc_method),
1,
{{(struct objc_selector *)"logHello", "v16@0:8", (void *)_C_SuperModel_Test_logHello}}
};
我们可以看到在Category
中添加的实例方法、类方法的IMP
指向的函数的具体实现;然后生成两个方法列表:
-
_OBJC_$_CATEGORY_INSTANCE_METHODS_SuperModel_$_Test
用来存储Category
中添加的所有实例方法; -
_OBJC_$_CATEGORY_CLASS_METHODS_SuperModel_$_Test
用来存储Category
中添加的所有类方法;
2.2.2、一些属性的实现
在 .cpp
文件找到关于Category
中添加的属性的关键代码:
static struct /*_prop_list_t*/ {
unsigned int entsize; // sizeof(struct _prop_t)
unsigned int count_of_properties;//列表中元素的数量
struct _prop_t prop_list[2];
} _OBJC_$_PROP_LIST_SuperModel_$_Test __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_prop_t),
2,//2 个属性
{{"nickName","T@\"NSString\",&,N"}},
{"age","T@\"NSString\",&,N"}}
};
我们可以看到在Category
中添加的属性被存储在属性列表_OBJC_$_PROP_LIST_SuperModel_$_Test
中。
在Category
中添加了两个属性,但是在上节中没有发现关于属性 age
的set
与 get
方法。也就是说:Category
中添加的属性系统并不会动态的创建set
与 get
方法,需要开发者去实现set
与 get
方法。
2.2.3、一些协议的实现
在 .cpp
文件找到关于Category
中实现的协议方法的关键代码:
//实现的 ObjectDelegate 协议的方法列表
static struct{
unsigned int entsize; // sizeof(struct _objc_method)
unsigned int method_count;//实现的方法数量
struct _objc_method method_list[1];//实现的方法列表
} _OBJC_PROTOCOL_INSTANCE_METHODS_ObjectDelegate __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_objc_method),
1,
{{(struct objc_selector *)"logObjectInfo", "v16@0:8", 0}}
};
//表示协议的结构体 _protocol_t
struct _protocol_t {
void * isa; // NULL
const char *protocol_name;//协议名称
const struct _protocol_list_t * protocol_list; //遵循的父协议
const struct method_list_t *instance_methods;//必须实现的实例方法列表
const struct method_list_t *class_methods;//必须实现的类方法列表
const struct method_list_t *optionalInstanceMethods;//可选的实例方法列表
const struct method_list_t *optionalClassMethods;//可选的类方法列表
const struct _prop_list_t * properties;//属性列表
const unsigned int size; // 该结构体实例的内存大小
const unsigned int flags; // = 0
const char ** extendedMethodTypes;
};
//ObjectDelegate 协议的结构体实例
struct _protocol_t _OBJC_PROTOCOL_ObjectDelegate __attribute__ ((used)) = {
0,
"ObjectDelegate",//协议名称
(const struct _protocol_list_t *)&_OBJC_PROTOCOL_REFS_ObjectDelegate,//遵循的父协议
(const struct method_list_t *)&_OBJC_PROTOCOL_INSTANCE_METHODS_ObjectDelegate,//必须实现的实例方法列表
0,//必须实现的类方法列表
0,//可选的实例方法列表
0,//可选的类方法列表
0,//属性列表
sizeof(_protocol_t),// 该结构体实例的内存大小
0,
(const char **)&_OBJC_PROTOCOL_METHOD_TYPES_ObjectDelegate
};
struct _protocol_t *_OBJC_LABEL_PROTOCOL_$_ObjectDelegate = &_OBJC_PROTOCOL_ObjectDelegate;
/* 实现的协议列表。
* 注意:不是实现的协议方法列表
*/
static struct /*_protocol_list_t*/ {
long protocol_count; // Note, this is 32/64 bit
struct _protocol_t *super_protocols[1];
} _OBJC_CATEGORY_PROTOCOLS_$_SuperModel_$_Test __attribute__ ((used, section ("__DATA,__objc_const"))) = {
1,
&_OBJC_PROTOCOL_ObjectDelegate//实现的 ObjectDelegate 协议
};
关于协议 _protocol_t
并不是本章研究的内容,在这里我们只是观察下协议 _protocol_t
是如何添加到 Category
的:
- 在
Category
中实现的协议,生成一个个_protocol_t
结构体实例:如实现的协议ObjectDelegate
转为结构体实例_OBJC_PROTOCOL_ObjectDelegate
; - 实现的协议列表存储着实现的一个个协议:如 协议列表
_OBJC_CATEGORY_PROTOCOLS_$_SuperModel_$_Test
存储实现的协议ObjectDelegate
的内存地址;
2.2.4、Category
的结构体实例
截止到目前,我们已经研究了Category
中添加实例方法、类方法,实现某些协议,添加属性的过程;接下来研究Category
的结构体实例:
//分类转为结构体:对结构体赋值
static struct _category_t _OBJC_$_CATEGORY_SuperModel_$_Test __attribute__ ((used, section ("__DATA,__objc_const"))) =
{
"SuperModel",//分类所属的类的名称
0, // &OBJC_CLASS_$_SuperModel, //分类所属的类
(const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_SuperModel_$_Test,//分类里添加的实例方法列表
(const struct _method_list_t *)&_OBJC_$_CATEGORY_CLASS_METHODS_SuperModel_$_Test,//分类添加的类方法列表
(const struct _protocol_list_t *)&_OBJC_CATEGORY_PROTOCOLS_$_SuperModel_$_Test,//分类实现的协议列表
(const struct _prop_list_t *)&_OBJC_$_PROP_LIST_SuperModel_$_Test,//分类添加的属性列表
};
static void OBJC_CATEGORY_SETUP_$_SuperModel_$_Test(void ) {
_OBJC_$_CATEGORY_SuperModel_$_Test.cls = &OBJC_CLASS_$_SuperModel;
}
__declspec(allocate(".objc_inithooks$B")) static void *OBJC_CATEGORY_SETUP[] = {
(void *)&OBJC_CATEGORY_SETUP_$_SuperModel_$_Test,
};
static struct _category_t *L_OBJC_LABEL_CATEGORY_$ [1] __attribute__((used, section ("__DATA, __objc_catlist,regular,no_dead_strip")))= {
&_OBJC_$_CATEGORY_SuperModel_$_Test,
};
我们可以在结构体实例 _OBJC_$_CATEGORY_SuperModel_$_Test
的结构成员分别是前文分析的 :
- 实例方法列表地址:
_OBJC_$_CATEGORY_INSTANCE_METHODS_SuperModel_$_Test
; - 类方法列表:
_OBJC_$_CATEGORY_CLASS_METHODS_SuperModel_$_Test
; - 实现的协议列表:
_OBJC_CATEGORY_PROTOCOLS_$_SuperModel_$_Test
; - 属性列表:
_OBJC_$_PROP_LIST_SuperModel_$_Test
;
此时 .cpp
文件研究完毕,我们清楚的了解 Category
是如何实现它所添加的方法、属性、协议的!但是我们的问题并没有找到答案:系统是如何加载Category
中的方法、属性、协议?
接下来去 Runtime源码 查询答案。
3、Category
如何加载 ?
load方法调用栈.png
3.1、Runtime 的入口函数
Objective-C 的运行是依赖于 Runtime 的:在 Runtime 库 的objc-os.mm
文件找到入口函数如下:
void _objc_init(void){
static bool initialized = false;
if (initialized) return;
initialized = true;
environ_init();//环境初始化,读取影响运行时的环境变量; 如果需要,还可以打印环境变量帮助。
tls_init();//初始化线程存储的键
static_init();//运行 C++ 静态构造函数
lock_init();// 锁的初始化
exception_init();//初始化 libobjc 的异常处理系统
/* 注册dyld事件的监听:
* 注册 unmap_image,以防某些 +load 取消映射
* map_images 函数是初始化的关键,内部完成了大量 Runtime 环境的初始化操作。
*/
_dyld_objc_notify_register(&map_images, load_images, unmap_image);
}
Runtime 的入口函数 _objc_init()
主要功能:
- 1、环境初始化,读取影响运行时的环境变量; 如果需要,还可以打印环境变量帮助;
- 2、初始化线程存储的键
_objc_pthread_key
; - 3、运行 C++ 静态构造函数;
- 4、锁的初始化
- 5、初始化 libobjc 的异常处理系统;
- 6、通过 dyld 调用
map_images()
函数,load_images()
函数,unmap_image()
函数
Category
被附加到类上是dyld
通过_dyld_objc_notify_register()
调用 map_images()
时发生的;
我们去看下map_images()
函数:
/* 处理被映射到 dyld 的指定镜像;
* 在加锁后,将任务交给 map_images_nolock() 函数
*/
void map_images(unsigned count, const char * const paths[],const struct mach_header * const mhdrs[]){
mutex_locker_t lock(runtimeLock);
//加了个锁,然后调用 map_images_nolock() 函数
return map_images_nolock(count, paths, mhdrs);
}
/* 处理被映射到 dyld 的一些镜像 mhdrs[],主要功能:
* 1、首次调用,初始化共享缓存
* 2、统计所有的 header_info ,统计所有的 class 数量,未优化的 class 数量;
* 3、首次调用,注册内部使用的选择器,初始化自动释放池与哈希表
* 4、第 2 步获取的信息,调用 _read_images() 函数来处理
*/
void map_images_nolock(unsigned mhCount, const char * const mhPaths[], const struct mach_header * const mhdrs[]){
//...代码省略
_read_images(hList, hCount);
//...代码省略
}
map_images_nolock()
函数根据入参 mhdrs[]
统计所有的header_info
,统计所有的 class
数量,未优化的 class
数量;然后调用_read_images()
函数去处理统计出来的信息。
3.2、_read_images()
函数
void _read_images(header_info **hList, uint32_t hCount){
//....以上代码省略
// 发现类别。
for (EACH_HEADER) {
category_t **catlist = _getObjc2CategoryList(hi, &count);
for (i = 0; i < count; i++) {
category_t *cat = catlist[i];
Class cls = remapClass(cat->cls);
if (!cls) {
// Category的目标类丢失了(可能是弱链接的)。
//推翻对此 Category 的任何了解。
catlist[i] = nil;
if (PrintConnecting) {
_objc_inform("CLASS: IGNORING category \?\?\?(%s) %p with missing weak-linked target class", cat->name, cat);
}
continue;
}
// 这个类别的过程:首先,向其目标类注册类别。
// 然后,如果实现了类,则重建类的方法列表。
bool classExists = NO;
if (cat->instanceMethods || cat->protocols || cat->instanceProperties) {
addUnattachedCategoryForClass(cat, cls, hi);
if (cls->isRealized()) {
remethodizeClass(cls);
classExists = YES;
}
if (PrintConnecting) {
_objc_inform("CLASS: found category -%s(%s) %s", cls->nameForLogging(), cat->name, classExists ? "on existing class" : "");
}
}
if (cat->classMethods || cat->protocols ) {
/* addUnattachedCategoryForClass() 为类添加独立的类别
* 该函数会对 Class 和 Category 做一个映射关联
*/
addUnattachedCategoryForClass(cat, cls->ISA(), hi);
if (cls->ISA()->isRealized()) {
/* remethodizeClass() 会把 Category 中的方法列表加到 Class 的methed_list_t里面去;
* 而且是插入到Class方法列表的前面
* 这就是 Category 中重写主类的方法导致的方法覆盖的原因
*/
remethodizeClass(cls->ISA());
}
if (PrintConnecting) {
_objc_inform("CLASS: found category +%s(%s)",
cls->nameForLogging(), cat->name);
}
}
}
}
ts.log("IMAGE TIMES: discover categories");
// Category 必须是最后一个查找,以避免潜在的竞争
// 当其他线程在此线程完成修复之前调用新类别代码时:
// +load handled by prepare_load_methods()
//....以下代码省略
}
网友评论