iOS进阶笔记(二)分类的使用和本质分析

📣 iOS进阶笔记目录


1、Category的使用

扩展原有类的功能:Category增加成员属性(通过关联对象方式)、方法(包括instance-method和class-method)、协议

下面以Person(继承NSObject)类为例

@interface Person (Method)
@property (nonatomic, assign) int age;

- (void)printAge;
@end

static const void *ageKey = &ageKey;
- (void)setAge:(int)age {
    objc_setAssociatedObject(self, ageKey, @(age), OBJC_ASSOCIATION_ASSIGN);
}

- (int)age {
    return [objc_getAssociatedObject(self, ageKey) intValue];
}
- (void)printAge {
    NSLog(@"年龄是:%d",self.age);
}

查看clang编译后的代码

static const void *ageKey = &ageKey;
// setAge: 方法本质是像NSNumber类,发送了numberWithInt:方法
static void _I_Person_Method_setAge_(Person * self, SEL _cmd, int age) {
    objc_setAssociatedObject(self, ageKey, ((NSNumber *(*)(Class, SEL, int))(void *)objc_msgSend)(objc_getClass("NSNumber"), sel_registerName("numberWithInt:"), (int)(age)), OBJC_ASSOCIATION_ASSIGN);
}

// getAge:方法本质是向self所持有的Number对象,发送了intValue方法,从而获取到age
static int _I_Person_Method_age(Person * self, SEL _cmd) {
    return ((int (*)(id, SEL))(void *)objc_msgSend)((id)objc_getAssociatedObject(self, ageKey), sel_registerName("intValue"));
}

static void _I_Person_Method_printAge(Person * self, SEL _cmd) {
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_75_mphjrxws47b6srgd_j5ypg000000gp_T_Person_Method_edc82b_mi_0,((int (*)(id, SEL))(void *)objc_msgSend)((id)self, sel_registerName("age")));
}

2、Category本质分析

1)通过clang分析Person(Method)本质

// 属性的定义
struct _prop_t {
	const char *name;
	const char *attributes;
};

struct _protocol_t;
// 方法的定义
struct _objc_method {
	struct objc_selector * _cmd;// SEL 实际是char *数组,存放的数字和imp指针对应
	const char *method_type;// 方法类型
	void  *_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;
};

// ro的结构
struct _class_ro_t {
	unsigned int flags;
	unsigned int instanceStart;
	unsigned int instanceSize;
	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;// 通过ro可以找到原有类的instanceSize 、方法列表、属性列表、 协议列表等
};

// 分类的定义
struct _category_t {
	const char *name;// name
	struct _class_t *cls;// 分类所属的class
	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;// 属性列表
};

//  _method_list_t结构体初始化
// instanceMethod列表的定义--即名称为`_OBJC_$_CATEGORY_INSTANCE_METHODS_Person_$_Method`的结构体
static struct /*_method_list_t*/ {
	unsigned int entsize;  // sizeof(struct _objc_method)
	unsigned int method_count;
	struct _objc_method method_list[3];//变长结构体数组,确定了3个方法(setAge: / age / printAge)
} _OBJC_$_CATEGORY_INSTANCE_METHODS_Person_$_Method __attribute__ ((used, section ("__DATA,__objc_const"))) = {
	sizeof(_objc_method),
	3,
	{{(struct objc_selector *)"setAge:", "v20@0:8i16", (void *)_I_Person_Method_setAge_},
	{(struct objc_selector *)"age", "i16@0:8", (void *)_I_Person_Method_age},
	{(struct objc_selector *)"printAge", "v16@0:8", (void *)_I_Person_Method_printAge}}
};

// 属性列表初始化
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_Person_$_Method __attribute__ ((used, section ("__DATA,__objc_const"))) = {
	sizeof(_prop_t),
	1,// 只有一个属性age
	{{"age","Ti,N"}}// 即name=age,attributes=Ti,N (表示为Type int, name-名称)
};

// 初始化一个类对象(类型为_class_t 的OBJC_CLASS_$_Person结构体)
extern "C" __declspec(dllimport) struct _class_t OBJC_CLASS_$_Person;

// 分类的初始化
static struct _category_t _OBJC_$_CATEGORY_Person_$_Method __attribute__ ((used, section ("__DATA,__objc_const"))) = 
{
	"Person",// name
	0, // &OBJC_CLASS_$_Person,即struct _class_t *cls
	(const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_Person_$_Method,// 实例方法列表(instance_methods)
	0,// 分类中没有类方法
	0,// 分类中没有协议
	(const struct _prop_list_t *)&_OBJC_$_PROP_LIST_Person_$_Method,// 分类中的属性列表
};

static void OBJC_CATEGORY_SETUP_$_Person_$_Method(void ) {
	_OBJC_$_CATEGORY_Person_$_Method.cls = &OBJC_CLASS_$_Person;// OBJC_CLASS_$_Person的地址赋值给_category_t结构体中cls
}

小结:
1)Person(Method)分类在初始化时传入了所属类("Person" )
2)所属类class初始化为0(在OBJC_CATEGORY_SETUP_$_Person_$_Method方法中会将OBJC_CLASS_$_Person的地址赋值给_category_t结构体中cls)
3)实例方法列表(使用变长结构体存储:setAge: / age / printAge三个方法)
4)类方法列表为0(Person+Method.h文件中没有定义分类的类方法,所以为0)
5)协议方法列表为0(Person+Method.h文件中没有定义协议,所以为0)
6)属性列表结构体地址(使用变长结构体,存储一个age属性)


2)通过runtime源码查看Category的本质

struct category_t {
    const char *name;
    classref_t cls;// classref_t is unremapped class_t*

    WrappedPtr<method_list_t, PtrauthStrip> instanceMethods;

    WrappedPtr<method_list_t, PtrauthStrip> 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;// 若当前类为meta-class,则返回class-method
        else return instanceMethods;// 否则返回instance-method
    }
    
    property_list_t *propertiesForMeta(bool isMeta, struct header_info *hi);
    
    protocol_list_t *protocolsForMeta(bool isMeta) {
        if (isMeta) return nullptr;// 若当前类为meta-class,则返回nil
        else return protocols;
    }
};


小结:
Category本质就是category_t类型的结构体

struct category_t {
    协议名称 name
    所属类 cls
    实例方法列表
    类方法列表
    协议列表
    实例对象属性列表
    类对象属性列表
}

以上


----------End------------

posted @ 2021-08-04 20:52  ITRyan  阅读(57)  评论(0编辑  收藏  举报