iOS:Category为什么不能直接添加成员变量却能添加方法

3,770 阅读2分钟

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中管理的,不管如何增删类方法,都不影响类实例的内存布局,已经创建出的类实例仍然可正常使用。