理解 Objective-C:Category

2017/03/16

想了解Category如何在运行时为OC的类添加方法的,根据Category实现原理,进一步确定Category的方法为什么会覆盖Class自身类的实现?

TODO:Swift的 class Extension,protocol Extension 又是如何给类添加方法的,当方法发生覆盖时,怎么确定哪一个方法被调用?函数的派发方式?

#import "MyClass.h"

@implementation MyClass

- (void)printName
{
    NSLog(@"%@",@"MyClass");
}

@end

@implementation MyClass(MyAddition)

- (void)printName
{
    NSLog(@"%@",@"MyAddition");
}

@end

经过 clang -rewrite-objc MyClass.m 命令看看Category底层结构体具体实现。

通过 clang -rewrite-objc 命令,仅将扩展语法通过可读性更高的 C 语法进行改写,而不是编译期中的子编译过程

// @implementation MyClass

// - (void)printName {   NSLog(@"%@",@"MyClass"); } 被编译成一个静态函数叫 _I_MyClass_printName 这个名字时编译器决定的.参数时传入 typedef struct obj_object MyClass 也就是 self指针,和 _cmd 调用NSLog()方法,参数就是这两个被定义在常量区的字符串。

static void _I_MyClass_printName(MyClass ` self, SEL _cmd) {
    NSLog((NSString `)&__NSConstantStringImpl__var_folders_tt_zt3fc7zx0xj2fnbc6r1l01n00000gn_T_main_ff014a_mi_0,(NSString `)&__NSConstantStringImpl__var_folders_tt_zt3fc7zx0xj2fnbc6r1l01n00000gn_T_main_ff014a_mi_1);
}

// @end

// @implementation MyClass(MyAddition)

// - (void)printName {   NSLog(@"%@",@"MyAddition"); }

static void _I_MyClass_MyAddition_printName(MyClass ` self, SEL _cmd) {
    NSLog((NSString `)&__NSConstantStringImpl__var_folders_tt_zt3fc7zx0xj2fnbc6r1l01n00000gn_T_main_ff014a_mi_2,(NSString `)&__NSConstantStringImpl__var_folders_tt_zt3fc7zx0xj2fnbc6r1l01n00000gn_T_main_ff014a_mi_3);
}
//编译器生成了实例方法列表OBJC$_CATEGORY_INSTANCE_METHODSMyClass$_MyAddition
//和属性列表OBJC$_PROP_LISTMyClass$_MyAddition,
//两者的命名都遵循了公共前缀+类名+category名字的命名方式


// @end

// 下面这些基础的结构体:_prop_t,_objc_method,_protocol_t,_ivar_t,_class_ro_t,_category_t....将用在编译器处理MyClass和MyAddition上。
struct _prop_t { //属性的结构体
	const char `name;
	const char `attributes;
};

struct _protocol_t;

struct _objc_method { //Method
	struct objc_selector ` _cmd; //SEL
	const char `method_type;
	void  `_imp;//IMP
};

struct _protocol_t {
	void ` isa;  // NULL
	const char `protocol_name;
	const struct _protocol_list_t ` protocol_list; // super protocols
	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;  // sizeof(struct _protocol_t)
	const unsigned int flags;  // = 0
	const char `` extendedMethodTypes;
};

struct _ivar_t {  //成员变量结构体
	unsigned long int `offset;  // pointer to ivar offset location
	const char `name;
	const char `type;
	unsigned int alignment;
	unsigned int  size;
};

struct _class_ro_t { // 类里面只读部分的结构
	unsigned int flags;
	unsigned int instanceStart;
	unsigned int instanceSize;
	unsigned int reserved;
	const unsigned char `ivarLayout;
	const char `name;
	const struct _method_list_t `baseMethods;
	const struct _objc_protocol_list `baseProtocols;
	const struct _ivar_list_t `ivars;
	const unsigned char `weakIvarLayout;
	const struct _prop_list_t `properties;
};

struct _class_t { //表达类结构
	struct _class_t `isa;
	struct _class_t `superclass;
	void `cache; //缓存列表
	void `vtable; //这还有个虚函数表
	struct _class_ro_t `ro; //只读的固定信息
};

struct _category_t { //category结构体
	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;
};
extern "C" __declspec(dllimport) struct objc_cache _objc_empty_cache;
#pragma warning(disable:4273)

static struct /`_method_list_t`/ {
	unsigned int entsize;  // sizeof(struct _objc_method)
	unsigned int method_count;
	struct _objc_method method_list[1];
} _OBJC_$_INSTANCE_METHODS_MyClass __attribute__ ((used, section ("__DATA,__objc_const"))) = {
	sizeof(_objc_method),
	1,
	
}; // 用于MyClass实例方法的结构体列表:_OBJC_$_INSTANCE_METHODS_MyClass 在指定section的存放 。

static struct _class_ro_t _OBJC_METACLASS_RO_$_MyClass __attribute__ ((used, section ("__DATA,__objc_const"))) = {
	1, sizeof(struct _class_t), sizeof(struct _class_t), 
	(unsigned int)0, 
	0, 
	"MyClass",
	0, 
	0, 
	0, 
	0, 
	0, 
};//生成MyClass元类只读信息部分的结构体

static struct _class_ro_t _OBJC_CLASS_RO_$_MyClass __attribute__ ((used, section ("__DATA,__objc_const"))) = {
	0, sizeof(struct MyClass_IMPL), sizeof(struct MyClass_IMPL), 
	(unsigned int)0, 
	0, 
	"MyClass",
	(const struct _method_list_t `)&_OBJC_$_INSTANCE_METHODS_MyClass,
	0, 
	0, 
	0, 
	0, 
};//生成MyClass类的结构体

extern "C" __declspec(dllimport) struct _class_t OBJC_METACLASS_$_NSObject;

extern "C" __declspec(dllexport) struct _class_t OBJC_METACLASS_$_MyClass __attribute__ ((used, section ("__DATA,__objc_data"))) = {
	0, // &OBJC_METACLASS_$_NSObject,
	0, // &OBJC_METACLASS_$_NSObject,
	0, // (void `)&_objc_empty_cache,
	0, // unused, was (void `)&_objc_empty_vtable,
	&_OBJC_METACLASS_RO_$_MyClass,
}; //生成MyClass的元类的结构体。

extern "C" __declspec(dllimport) struct _class_t OBJC_CLASS_$_NSObject;

extern "C" __declspec(dllexport) struct _class_t OBJC_CLASS_$_MyClass __attribute__ ((used, section ("__DATA,__objc_data"))) = {
	0, // &OBJC_METACLASS_$_MyClass,
	0, // &OBJC_CLASS_$_NSObject,
	0, // (void `)&_objc_empty_cache,
	0, // unused, was (void `)&_objc_empty_vtable,
	&_OBJC_CLASS_RO_$_MyClass,
};//生产MyClass的类结构体


static void OBJC_CLASS_SETUP_$_MyClass(void ) {
	OBJC_METACLASS_$_MyClass.isa = &OBJC_METACLASS_$_NSObject;
	OBJC_METACLASS_$_MyClass.superclass = &OBJC_METACLASS_$_NSObject;
	OBJC_METACLASS_$_MyClass.cache = &_objc_empty_cache;
	OBJC_CLASS_$_MyClass.isa = &OBJC_METACLASS_$_MyClass;
	OBJC_CLASS_$_MyClass.superclass = &OBJC_CLASS_$_NSObject;
	OBJC_CLASS_$_MyClass.cache = &_objc_empty_cache;
}//在内存中拼接MyClass所有信息的接口,用于之后统一所有类载入内存用

#pragma section(".objc_inithooks$B", long, read, write)
__declspec(allocate(".objc_inithooks$B")) static void `OBJC_CLASS_SETUP[] = {
	(void `)&OBJC_CLASS_SETUP_$_MyClass,
};//在内存中统一设置所有Objc类结构。

static struct /`_method_list_t`/ {
	unsigned int entsize;  // sizeof(struct _objc_method)
	unsigned int method_count;
	struct _objc_method method_list[1];
} _OBJC_$_CATEGORY_INSTANCE_METHODS_MyClass_$_MyAddition __attribute__ ((used, section ("__DATA,__objc_const"))) = {
	sizeof(_objc_method),
	1,
	
};//生成MyClass(MyAddition)的Category的方法列表结构体,用于生成category_t

static struct /`_prop_list_t`/ {
	unsigned int entsize;  // sizeof(struct _prop_t)
	unsigned int count_of_properties;
	struct _prop_t prop_list[1];
} _OBJC_$_PROP_LIST_MyClass_$_MyAddition __attribute__ ((used, section ("__DATA,__objc_const"))) = {
	sizeof(_prop_t),
	1,
	name
};//生产MyClass(MyAddition)的Category的属性列表结构体,用于生成category_t

extern "C" __declspec(dllexport) struct _class_t OBJC_CLASS_$_MyClass;

static struct _category_t _OBJC_$_CATEGORY_MyClass_$_MyAddition __attribute__ ((used, section ("__DATA,__objc_const"))) = 
{
	"MyClass",
	0, // &OBJC_CLASS_$_MyClass,
	(const struct _method_list_t `)&_OBJC_$_CATEGORY_INSTANCE_METHODS_MyClass_$_MyAddition,
	0,
	0,
	(const struct _prop_list_t `)&_OBJC_$_PROP_LIST_MyClass_$_MyAddition,
};//最后拼接成MyClass(MyAddition)的Category的完整结构。
static void OBJC_CATEGORY_SETUP_$_MyClass_$_MyAddition(void ) {
	_OBJC_$_CATEGORY_MyClass_$_MyAddition.cls = &OBJC_CLASS_$_MyClass;
}//为Category(MyAddition)设置扩展源类,拿到原类结构体后指针应该会对原类有修改eg:插入扩展方法。
#pragma section(".objc_inithooks$B", long, read, write)
__declspec(allocate(".objc_inithooks$B")) static void `OBJC_CATEGORY_SETUP[] = {
	(void `)&OBJC_CATEGORY_SETUP_$_MyClass_$_MyAddition,
};
static struct _class_t `L_OBJC_LABEL_CLASS_$ [1] __attribute__((used, section ("__DATA, __objc_classlist,regular,no_dead_strip")))= {
	&OBJC_CLASS_$_MyClass,
};
static struct _category_t `L_OBJC_LABEL_CATEGORY_$ [1] __attribute__((used, section ("__DATA, __objc_catlist,regular,no_dead_strip")))= {
	&_OBJC_$_CATEGORY_MyClass_$_MyAddition,
};
static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };

上面的MyClass类和它的Category经过Clang编译器。翻译成了C的各种结构体与函数,将它们拼接起来在内存中,表达完整的OC类内存模型。

之后看看这些生成的category结构体 如何通过运行时代码实现扩展类功能的办法。

下面是运行时代码入口,Category会一步步将自己的方法插入Class的class_rw_t的methods里面。重复的key就会覆盖。

void _objc_init(void)
└──const char `map_2_images(...)
    └──const char `map_images_nolock(...)
        └──void _read_images(header_info ``hList, uint32_t hCount)

下面是添加category给class片段

if (cat->classMethods  ||  cat->protocols  
    /` ||  cat->classProperties `/) {
    addUnattachedCategoryForClass(cat, cls->ISA(), hi);
    if (cls->ISA()->isRealized()) {
        remethodizeClass(cls->ISA());
    }
}

addUnattachedCategoryForClass 函数语意上就是添加还没有被添加的Category给Class。

remethodizeClass(cls->ISA()); 重新调整class的methods结构体。

static void addUnattachedCategoryForClass(category_t `cat, Class cls,
                                          header_info `catHeader)
{
    runtimeLock.assertWriting();

    NXMapTable `cats = unattachedCategories(); //这里获得一张巨大的静态表,里面有所有还没添加到class上的Category
    category_list `list;

    list = (category_list `)NXMapGet(cats, cls); //以cls为key在表中查找属于这个class类的Category的方法列表list。
    if (!list) {
        list = (category_list `)
            calloc(sizeof(`list) + sizeof(list->list[0]), 1);
    } else {
        list = (category_list `)
            realloc(list, sizeof(`list) + sizeof(list->list[0]) ` (list->count + 1));
    }
    list->list[list->count++] = (locstamped_category_t){cat, catHeader};
    NXMapInsert(cats, cls, list); //将这个获得的方法列表list插入这张大表里 以cls为key,list为value。
}

static void remethodizeClass(Class cls)
{
		//....///
    if ((cats = unattachedCategoriesForClass(cls, false/`not realizing`/))) {
		//....///
        attachCategories(cls, cats, true /`flush caches`/);        
        free(cats);
    }
}

attachCategories里就会将方法列表重新写到cls->data()-> methods里面。

static void 
attachCategories(Class cls, category_list `cats, bool flush_caches)
{
    method_list_t ``mlists = (method_list_t ``)
        malloc(cats->count ` sizeof(`mlists));
    while (i--) {
        method_list_t `mlist = entry.cat->methodsForMeta(isMeta);
        if (mlist) {
            mlists[mcount++] = mlist;
            fromBundle |= entry.hi->isBundle();
        }
    } //mlists 为我们要的方法列表指针

    auto rw = cls->data();

    prepareMethodLists(cls, mlists, mcount, NO, fromBundle);
    rw->methods.attachLists(mlists, mcount);//添加mlists方法到cls-> data()->methods里
    free(mlists);
    if (flush_caches  &&  mcount > 0) flushCaches(cls);

    rw->properties.attachLists(proplists, propcount);
    free(proplists);

    rw->protocols.attachLists(protolists, protocount);
    free(protolists);
}

Post Directory