Category原理:
先看一下Category的一个数据结构、处理方案
//Category表示一个结构体指针的类型
typedef struct objc_category *Category;
typedef struct category_t {
const char *name;//类的名字 主类名字
classref_t cls;//类
struct method_list_t *instanceMethods;//实例方法的列表
struct method_list_t *classMethods;//类方法的列表
struct protocol_list_t *protocols;//所有协议的列表
struct property_list_t *instanceProperties;//添加的所有属性
} category_t;
OBJC2_UNAVAILABLE;
解读:
1、Category其实是一个Category_t结构体,在编译时会把Category转换成Category_t.
2、关联的对象以哈希表形式存储在一个全局的单例里面
3、在运行时的时候,runtime初始化的时候,会调用_objc_init,里面调用一个叫做reMethodizeClass,函数内部会调用attachCategories,传入category和class,会将方法列表,协议列表等与原有的类合并
Category为什么不能直接添加成员变量:
首先,我们先看一下Class的定义
//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;
对比可以发现,category_t中少了 struct objc_ivar_list * _Nullable ivars
也就是说没有存储ivar数组(成员变量数组)
结合category与原类的结合时机总结:分类并不会改变原有类的内存分布的情况,它是在运行期间决定的,此时内存的分布已经确定,若此时再添加实例会改变内存的分布情况,这对编译性语言是灾难,是不允许的。
Category为什么能直接添加方法和属性:
因为方法和属性并不“属于”类实例,而成员变量“属于”类实例。我们所说的“类实例”概念,指的是一块内存区域,包含了isa指针和所有的成员变量。所以假如允许动态修改类成员变量布局,已经创建出的类实例就不符合类定义了,变成了无效对象。但方法定义是在objc_class中管理的,不管如何增删类方法,都不影响类实例的内存布局,已经创建出的类实例仍然可正常使用。