OC

Category

Posted by sunzhongliang on September 10, 2019

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] , 转载请保留原文链接.