category
category的本质
category是通过运行时机制,通过Runtime动态将分类的方法合并到类对象、元类对象中
新建一个category,通过 xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc Person+test.m
指令将文件转为C++源码
可以看到cpp文件里面有这么一个结构体:
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_MyPerson_$_Test __attribute__ ((used, section ("__DATA,__objc_const"))) =
{
"MyPerson",
0, // &OBJC_CLASS_$_MyPerson,
(const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_MyPerson_$_Test,
(const struct _method_list_t *)&_OBJC_$_CATEGORY_CLASS_METHODS_MyPerson_$_Test,
0,
0,
};
最终category会生成这样一个东西,说明编译完成后最终会产生_cagetory_t的结构体对象
如果再新建一个Eat的分类,那么会再次生成一个后缀为Eat的变量:
static struct _category_t _OBJC_$_CATEGORY_MyPerson_$_Eat __attribute__ ((used, section ("__DATA,__objc_const"))) =
{
"MyPerson",
0, // &OBJC_CLASS_$_MyPerson,
(const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_MyPerson_$_Eat,
(const struct _method_list_t *)&_OBJC_$_CATEGORY_CLASS_METHODS_MyPerson_$_Eat,
0,
0,
};
category的加载处理过程
- 通过Runtime加载某个类的所有Category数据
- 把所有Category的方法、属性、协议数据,合并到一个大数组中
- 后面参与编译的Category数据,会在数组的前面
- 将合并后的分类数据(方法、属性、协议),插入到类原来数据的前面
Category可以动态添加属性,但是不能添加实例变量。
原因:分类没有自己的isa指针.分类的定义:
//Category表示一个结构体指针的类型
typedef struct objc_category *Category;
struct objc_category {
char * _Nonnull category_name OBJC2_UNAVAILABLE;
char * _Nonnull class_name OBJC2_UNAVAILABLE;
struct objc_method_list * _Nullable instance_methods OBJC2_UNAVAILABLE;
struct objc_method_list * _Nullable class_methods OBJC2_UNAVAILABLE;
struct objc_protocol_list * _Nullable protocols OBJC2_UNAVAILABLE;
} OBJC2_UNAVAILABLE;
类的定义:
//Class也表示一个结构体指针的类型
typedef struct objc_class *Class;
struct objc_class {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Class _Nullable super_class OBJC2_UNAVAILABLE;
const char * _Nonnull name OBJC2_UNAVAILABLE;
long version OBJC2_UNAVAILABLE;
long info OBJC2_UNAVAILABLE;
long instance_size OBJC2_UNAVAILABLE;
struct objc_ivar_list * _Nullable ivars OBJC2_UNAVAILABLE;
struct objc_method_list * _Nullable * _Nullable methodLists OBJC2_UNAVAILABLE;
struct objc_cache * _Nonnull cache OBJC2_UNAVAILABLE;
struct objc_protocol_list * _Nullable protocols OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;
对比一下可以发现分类中少了struct objc_ivar_list * _Nullable ivars
,也就是说没有Ivar
数组,
这就是为什么分类里面不能增加成员变量的原因。如果强行添加属性,也能正常编译,如果在控制器里调用这个属性,程序运行时会报错,显示找不到该方法
如果必须要在分类当中使用属性的话,可以实现getter和setter方法
//这里用@selector(color)来用作 const void *key 的指针
- (UIColor *)color {
return objc_getAssociatedObject(self, _cmd);
}
- (void)setColor:(UIColor *)color {
objc_setAssociatedObject(self, @selector(color), color,OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
本文首次发布于 孙忠良 Blog, 作者 [@sunzhongliang] , 转载请保留原文链接.