OC中为类添加一个分类(Category)可以实现为类添加对象方法、类方法、添加属性(添加的属性不生成成员变量)、遵守协议。那么:
- 分类(Category)是什么?
- 对象方法和类方法怎么实现在方法调用时覆盖类中定义的方法?
- 添加的属性怎么实现修改和访问?
- 分类遵守的协议时是在做什么?
一、分类(Category)的结构
我们写一个分类,通过命令行指令生成C++代码文件
cd Person+TestProperty.m所在文件夹
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc Person+TestProperty.m
查看C++文件发现分类的数据类型是一个结构体_category_t
, 并且一个分类就是一个结构体变量,即在编译OC生成C++代码时,一个分类就会变成一个结构体变量。
// 分类的数据类型:结构体_category_t
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;
};
// 分类是一个结构体变量
static struct _category_t _OBJC_$_CATEGORY_Person_$_TestProperty __attribute__ ((used, section ("__DATA,__objc_const"))) =
{
"Person",
0, // &OBJC_CLASS_$_Person,
(const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_Person_$_TestProperty,
(const struct _method_list_t *)&_OBJC_$_CATEGORY_CLASS_METHODS_Person_$_TestProperty,
0,
(const struct _prop_list_t *)&_OBJC_$_PROP_LIST_Person_$_TestProperty,
};
二、方法调用时覆盖类中定义的方法?
查看OC的源码,
- 查找源码中
category_t {
, 可以验证分类是一个结构体,内部包含了对象方法列表instanceMethods
和类方法列表classMethods
。
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);
};
- 因为方法覆盖肯定会对这个结构体中
instanceMethods
操作,搜索可找到如下代码:
while (i--) {
auto& entry = cats->list[i];
method_list_t *mlist = entry.cat->methodsForMeta(isMeta);
if (mlist) {
mlists[mcount++] = mlist;
fromBundle |= entry.hi->isBundle();
}
property_list_t *proplist =
entry.cat->propertiesForMeta(isMeta, entry.hi);
if (proplist) {
proplists[propcount++] = proplist;
}
protocol_list_t *protolist = entry.cat->protocols;
if (protolist) {
protolists[protocount++] = protolist;
}
}
auto rw = cls->data();
prepareMethodLists(cls, mlists, mcount, NO, fromBundle);
rw->methods.attachLists(mlists, mcount);
free(mlists);
// cls->data()
class_rw_t* data() {
return (class_rw_t *)(bits & FAST_DATA_MASK);
}
追踪代码里面可以发现:
- 分类的对象方法列表会添加到类对象的方法列表(二维数组)中;
- 添加的方式是整个分类的方法列表添加到类对象的方法列表中作为一个元素;
- 越在后面编译的分类就越先添加到
mlists
中;(因为while (i--)从后往前添加)。 - 遍历所有分类,方法都添加到
mlists
中之后,把mlists
经过attachLists
函数处理放进类的class_rw_t
成员变量methods
中,类本身的方法列表会放到最后面。所以在方法调用时,从类的class_rw_t
的方法列表methods
中查找方法是从第一个元素开始查找,第一个元素也就是从最先添加(最后编译的那个分类)到里面的那个分类的方法列表,找到后会得到执行而后面的列表中有这个方法也会得不到执行,实现了方法的覆盖。
- 分类的方法列表什么时候添加的?这个就是顺着上述代码的调用查看:
_objc_init
->map_2_images
->map_images_nolock
->_read_images
-> 上图中显示的位置,进行分类信息合并到类信息中的操作。
_objc_init函数是源头,查看这个函数的解释:
可阅读文章 iOS底层探索之_objc_init
_objc_init是在APP冷启动时调用的,dyld
加载所有文件和动态库中符号和调用类和分类load方法,所以我们知道了,在启动时runtime环境初始化的时候,分类的方法列表就会合并到类中方法列表里面。
程序冷启动的过程:
iOS 冷启动
iOS 程序 main 函数之前发生了什么
三、分类中的属性怎么实现存取的?
从源码中,可以看到分类的属性也是像方法列表一样添加到类的属性列表中(遵守的协议也是一样),分类中@Property声明的属性是不会生成成员变量的。
通过属性关联,见博客:OC属性关联的实现原理
四、分类中的协议做什么的,为什么类中要用数组保存遵守的协议?
遵守一个协议即拥有协议中方法的声明,保存类遵守的协议,比如有如下判断则需要到协议列表中查找。
BOOL isConforms = [self conformsToProtocol:NSProtocolFromString(@"UITableViewDelegate")];
五、类扩展与分类的区别?
- 类扩展是在编译时就会将方法和属性合并到类中的,类扩展中声明属性和方法跟类是一样的效果。
- 分类中的方法是在冷启动时添加到类的方法列表中的,分类中声明的属性不会生成成员变量,分类中的属性需要通过属性关联来实现存取属性值。
附:
- 类class的构成如下,
class_rw_t
存在于bits当中。
struct objc_class : objc_object {
// Class ISA;
Class superclass;
cache_t cache; // 方法缓存
class_data_bits_t bits; // bits的内容就是class_rw_t
}
struct class_rw_t {
// Be warned that Symbolication knows the layout of this structure.
uint32_t flags;
uint32_t version;
const class_ro_t *ro;// 只读,里面存放了类的信息
method_array_t methods; // 类的所有方法列表
property_array_t properties;
protocol_array_t protocols;
Class firstSubclass;
Class nextSiblingClass;
char *demangledName;
}