Category
01-基本使用
Category的使用场合是什么?
为现有的类添加的新的方法,又不想改原来的类的时候
Category的实现原理
Category编译之后的底层结构是struct category_t,里面存储着分类的对象方法、类方法、属性、协议信息
在程序运行的时候,runtime会将Category的数据,合并到类信息中(类对象、元类对象中)
Category和Class Extension的区别是什么?
Class Extension在编译的时候,它的数据就已经包含在类信息中
Category是在运行时,才会将数据合并到类信息中
我们平时方法的调用就是发消息,他会根据isa根据找到类对象,去找方法,一个类只存在一个类对象,他的分类的方法存在于类对象之中,合并一起,如果是调的用是分类中类方法,他会的类方法存在于元类对象之中。
Category的地层结构
分类的合并并不是编译的时候,是在程序的运行的时候合并的,我们可以转成C++代码来查看,他首先会把分类的中的方法放到一个结构体里面,运行的时候才把他合并到类对象里面。
编译的时候 每一个分类都是在内存中转成下面这种结构体
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);
protocol_list_t *protocolsForMeta(bool isMeta) {
if (isMeta) return nullptr;
else return protocols;
}
};
03源码分析
运行时,他会将每一个分类的对象想法 合并到类对象中,将类方法合并到元类对象之中。
查看远嘛文件
objc-os.mm//运行的入口
objc_init//运行时的初始化
map_images //
map_images_nolock
objc_runtime_new.mm
_read_images//镜像。模块.,他会读取所有的类信息,这里面会有一个Discover categories
重新组织方法,将分类的方法加到了原来的数组列表里面
remethodizeClass
attachCategories
attachLists 将所有分类方法附加到,类对象的方法列表中
realloc、memmove、 memcpy
1通过Runtime加载某个类的所有Category数据
2把所有Category的方法、属性、协议数据,合并到一个大数组中
后面参与编译的Category数据,会在数组的前面
3将合并后的分类数据(方法、属性、协议),插入到类原来数据的前面
04源码分析02
attachLists内部做的事情,他会从新分布内存,旧的内存加上新的内存
void attachLists(List* const * addedLists, uint32_t addedCount) {
if (addedCount == 0) return;
if (hasArray()) {
// many lists -> many lists
//array()原来的方法列表
uint32_t oldCount = array()->count;
uint32_t newCount = oldCount + addedCount;
setArray((array_t *)realloc(array(), array_t::byteSize(newCount)));
array()->count = newCount;
memmove(array()->lists + addedCount, array()->lists,
oldCount * sizeof(array()->lists[0]));
//addedLists 所有分类的方法列表
memcpy(array()->lists, addedLists,
addedCount * sizeof(array()->lists[0]));
}
else if (!list && addedCount == 1) {
// 0 lists -> 1 list
list = addedLists[0];
}
else {
// 1 list -> many lists
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;
memcpy(array()->lists, addedLists,
addedCount * sizeof(array()->lists[0]));
}
}
他会将原来的方法列表里面的数据,往后移动,然后将分类的方法列表数据添加到原来方法列表所在的地方。所以同样的方法,他会优先调用分类的方法,因为数组要进行两次,先添加到addlist里面,在添加到原来的方法类表里,所以最后编译的分类放到了,合并之后的方法列表的最前面。
分类的方法覆盖并不是真的覆盖,他只是放在了原来类对象的方法前面
Category和Class Extension的区别是什么?
@interface SLPerson()
@end
Class Extension在编译的时候,它的数据就已经包含在类信息中
Category是在运行时,才会将数据合并到类信息中
这里面的东西,编译的时候就合并到了类对象里面。分类是运行时在添加类对象里面
05-memmove、memcpy区别
memcpy 如果我们使用memcpy把两个数据往后移动一下,3 4 51,
他会先移动第一个3-> 3 3 5 1,然后在去移动第二位的数据-> 3 3 3 1。这样并不能达到我们想要的效果,
如果我们想要使用memmove他会根据内存的地址大小,来判断挪动的方向-> 3 3 4 5 。他可以保证原来的数据的完整性
06答疑
在类对象的结构体里面的class_rw_t里面有个ro表,它里面存在着类原来的数据包括baseMethodList,这里面就不包括分类的方法列表