OC原理-Category

1,255 阅读3分钟

一、分类的本质结构

@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在运行时,才会将数据合并到类信息中。