MOFASHY

Live Is Life

iOS Category实现原理解析

在iOS开发中,Category(类别)是Objective-C中的一个强大特性,允许开发者在不修改原始类源代码的情况下,给类添加方法或属性。这一特性主要通过Objective-C的运行时特性实现。下面将详细介绍Category的实现原理。


一、Category底层数据结构

Objective-C中的Category在runtime中被表示为struct category_t结构体,其定义包含分类名称、所属类名、实例方法列表、类方法列表、协议列表等信息。具体结构如下:

1
2
3
4
5
6
7
8
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中的方法会被编译到这个结构体中,但尚未添加到类对象或元类对象中。


二、Category加载机制

运行时加载过程

  1. 初始化入口‌:runtime初始化时通过_objc_init函数注册map_images、load_images等回调函数。
  2. 映射阶段‌:map_images_nolock函数调用_read_images读取模块信息。
  3. 方法合并‌:运行时动态将Category的方法添加到类对象的方法列表中,具体操作是将分类方法插入到主类方法列表的前面。这种机制导致分类方法看似”覆盖”了原类方法,实际上是因查找顺序优先而被调用。

特殊方法调用

  • +load方法‌:在dyld装载程序初始化runtime环境时调用,所有类、分类的+load方法都会被执行,调用顺序为:父类->子类->分类。
  • +initialize方法‌:首次使用类时调用一次,采用消息机制调用,因此会受到分类方法”覆盖”影响。

三、Category与Extension的区别


特性 Category Extension
作用 为已有类添加方法 声明私有方法或属性
文件位置 独立的.h和.m文件 类的.m文件中声明
加载时机 运行时动态加载 编译时合并到类中
命名 必须命名(如ClassName+CategoryName) 匿名
属性支持 需通过关联对象实现 自动生成实例变量
方法覆盖 可能覆盖原类方法 编译时报错(若未实现)
可见性 公开(需导入头文件) 私有(仅类内部可见)

四、Category的局限性及注意事项

  1. 无法添加实例变量‌:Category结构体中没有成员变量列表,只能添加方法。
  2. 方法名冲突:同名方法调用优先级为:分类 > 本类 > 父类;多个分类中的同名方法由编译顺序决定调用哪个。
  3. 属性处理‌:虽然可以声明@property,但不会自动生成setter/getter方法和实例变量,需通过关联对象实现。
  4. 使用建议‌
    • 避免重写系统方法或第三方库方法。
    • 大型项目中按功能模块划分Category。
    • 为系统类扩展功能时优先使用Category。

五、关联对象实现属性

虽然Category不能直接添加实例变量,但可以通过关联对象技术间接实现属性存储:

1
2
3
4
5
// 添加关联对象
objc_setAssociatedObject(obj, &key, value, OBJC_ASSOCIATION_RETAIN_NONATOMIC);

// 获取关联对象
id value = objc_getAssociatedObject(obj, &key);

这种方法允许Category”拥有”属性,但需注意内存管理策略的选择。

Category作为Objective-C的重要特性,通过runtime的动态加载机制实现了对类的非侵入式扩展,为iOS开发提供了极大的灵活性和模块化能力。