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------------
(限于水平,本文可能存在瑕疵甚至错误的地方。如有发现,还请留言指正,相互学习。thx! )
KEEP LOOKING, DON`T SETTLE!