一、分类的本质结构
@interface Person (run)
@property(nonatomic,assign)int age;
-(void)instanceMethod;
-(void)classMethod;
@end
@implementation Person (run)
-(void)instanceMethod{
NSLog(@"%s",__func__);
}
+(void)classMethod{
NSLog(@"%s",__func__);
}
@end
执行命令把上诉代码转化c++
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc Person+run.m -o Person+run.cpp
//类的底层机构
struct _class_t {
struct _class_t *isa;
struct _class_t *superclass;
void *cache;
void *vtable;
struct _class_ro_t *ro;
};
//分类底层机构
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_$_run __attribute__ ((used, section ("__DATA,__objc_const"))) =
{
"Person",
0, // &OBJC_CLASS_$_Person,
(const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_Person_$_run,
(const struct _method_list_t *)&_OBJC_$_CATEGORY_CLASS_METHODS_Person_$_run,
0,
(const struct _prop_list_t *)&_OBJC_$_PROP_LIST_Person_$_run,
};
二、分类信息合并到原有类的具体过程
添加分类信息的源码 嘉定我们原有类有两个分类:分类1和分类2,且分类1比分类2先进行编译。
struct locstamped_category_t {
category_t *cat; //分类结构体
struct header_info *hi;
};
static void
attachCategories(Class cls, const locstamped_category_t *cats_list, uint32_t cats_count,
int flags)
{
//cls是类对象|元类对象 cats_list:存放改类的所有分类结构体 表现为[category_t(代表分类1的结构体),category_t(代表分类2的结构体)]
constexpr uint32_t ATTACH_BUFSIZ = 64;
method_list_t *mlists[ATTACH_BUFSIZ]; //用来存放该类的全部分类的方法列表 最后表现为[[method_t,method_t],[method_t,method_t]]
property_list_t *proplists[ATTACH_BUFSIZ]; //用来存放该类的全部分类的属性列表 最后表现为[[property_t,property_t],[property_t,property_t]]
protocol_list_t *protolists[ATTACH_BUFSIZ];//用来存放该类的全部分类的协议列表 最后表现为[[protocol_t,protocol_t],[protocol_t,protocol_t]]
uint32_t mcount = 0;
uint32_t propcount = 0;
uint32_t protocount = 0;
bool fromBundle = NO;
bool isMeta = (flags & ATTACH_METACLASS);
//取出类的信息表 即class_rw_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);
rwe->methods.attachLists(mlists, mcount);
mcount = 0;
}
//从后往前存放刚才取出的方法列表 即后取出来的放到前面 这里解释了后编译的分类优先级高
mlists[ATTACH_BUFSIZ - ++mcount] = mlist;
fromBundle |= entry.hi->isBundle();
}
//取出分类结构体中中的属性列表
property_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;
}
}
//操作完mlists大概是这样[[分类2的方法1,分类2的方法2,...],[分类1的方法1,分类1的方法2,...]]
if (mcount > 0) {
prepareMethodLists(cls, mlists + ATTACH_BUFSIZ - mcount, mcount, NO, fromBundle);
//把分类的方法列表合并到类对象中
rwe->methods.attachLists(mlists + ATTACH_BUFSIZ - mcount, mcount);
if (flags & ATTACH_EXISTING) flushCaches(cls);
}
//把分类的属性列表合并到类对象中
rwe->properties.attachLists(proplists + ATTACH_BUFSIZ - propcount, propcount);
//把分类的协议列表合并到类对象中
rwe->protocols.attachLists(protolists + ATTACH_BUFSIZ - protocount, protocount);
}
void attachLists(List* const * addedLists, uint32_t addedCount) {
if (addedCount == 0) return;
if (hasArray()) {
// many lists -> many lists
//取出原来类class_rw_t表中method_array_t的大小 :1
uint32_t oldCount = array()->count;
//addedCount可以理解为这个类的分类数量
uint32_t newCount = oldCount + addedCount;
//重新给class_rw_t表中method_array_t分配大小
setArray((array_t *)realloc(array(), array_t::byteSize(newCount)));
array()->count = newCount;
//这里解释了分类的优先级高与原类
//移动原来类的数据到最后,
memmove(array()->lists + addedCount, array()->lists,
oldCount * sizeof(array()->lists[0]));
//把要添加的方法|属性|协议数组移动到class_rw_t表中method_array_t前面
memcpy(array()->lists, addedLists,
addedCount * sizeof(array()->lists[0]));
}
}
运行时添加分类方法前class_rw_t->method_array_t是这样的
[[原来类的方法1,原来类的方法2,....]]
添加分类方法后class_rw_t->method_array_t是这样的
[[分类2的方法1,分类2的方法2,....],[分类1的方法1,分类1的方法2,....],[原来类的方法1,原来类的方法2,....]]。
通过上面的源码我们可以知道了 ,为何分类中的方法能覆盖掉原有类的方法,以及后编译的分类会覆盖掉先编译分类的方法。
三、总结
结论:编译后,分类添加的方法、属性、协议都会存放到_category_t结构体中,然后程序运行的时候,再通过runtime将实例方法,属性和协议存放到类对象中,将类方法存放到元类对象中。
补充:Category和类扩展的不同:类扩展在编译的时候,它的数据就已经包含在类信息中。Category在运行时,才会将数据合并到类信息中。