iOS深入理解Category

162 阅读2分钟

Category编译之后的底层结构是struct category_t,里面存储着分类的对象方法、类方法、属性、协议信息 在程序运行的时候,runtime会将Category的数据,合并到类信息中(类对象、元类对象中)

Category的底层结构

定义在objc-runtime-new.h中

    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; // 属性
    // 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;
        else return instanceMethods;
    }

    property_list_t *propertiesForMeta(bool isMeta, struct header_info *hi);
    
    protocol_list_t *protocolsForMeta(bool isMeta) {
        if (isMeta) return nullptr;
        else return protocols;
    }
};
Category的加载处理过程
  1. 通过Runtime加载某个类的所有Category数据
  2. 把所有Category的方法、属性、协议数据,合并到一个大数组中
  3. 后面参与编译的Category数据,会在数组的前面
  4. 将合并后的分类数据(方法、属性、协议),插入到类原来数据的前面
+load方法与+initialize方法
+load方法

+load方法会在runtime加载类、分类时调用

每个类、分类的+load,在程序运行过程中只调用一次

调用顺序

  • 先调用类的+load

  • 按照编译先后顺序调用(先编译,先调用)

  • 调用子类的+load之前会先调用父类的+load

  • 再调用分类的+load

  • 按照编译先后顺序调用(先编译,先调用)

+load方法是根据方法地址直接调用,并不是经过objc_msgSend函数调用

+initialize方法

调用顺序 先调用父类的+initialize, 再调用子类的+initialize (先初始化父类,再初始化子类,每个类只会初始化1次)

+initialize和+load的很大区别是

+initialize是通过objc_msgSend进行调用的,所以有以下特点 如果子类没有实现+initialize,会调用父类的+initialize(所以父类的+initialize可能会被调用多次) 如果分类实现了+initialize,就覆盖类本身的+initialize调用

如何实现给分类“添加成员变量”?

默认情况下,因为分类底层结构的限制,不能添加成员变量到分类中。但可以通过关联对象来间接实现 关联对象提供了以下API

添加关联对象
void objc_setAssociatedObject(id object, const void * key,
                                id value, objc_AssociationPolicy policy)

获得关联对象
id objc_getAssociatedObject(id object, const void * key)

移除所有的关联对象
void objc_removeAssociatedObjects(id object)
  • 关联对象并不是存储在被关联对象本身内存中
  • 关联对象存储在全局的统一的一个AssociationsManager中
Category和Class Extension的区别是什么?
  • Class Extension在编译的时候,它的数据就已经包含在类信息中
  • Category是在运行时,才会将数据合并到类信息中
  • 类扩展中申明的方法没有被实现,编译器会报警,但在分类中的方法没有被实现编译器是不会有任何的警告的,这是因为类扩展是在编译阶段被添加到类中,而分类则是在运行时添加到类中的。