9-1.【OC】【Category】Category 可以做什么?不能做什么?

3 阅读3分钟

在 Objective-C 中,Category(分类) 是一种极其灵活的运行时机制,它允许你在不改变原类源码、不继承原类的情况下,动态地为现有的类添加功能。

我们可以将其核心能力概括为:“能扩展行为,但不能扩展内存布局”。


1. Category 可以做什么?

Category 的本质是向类的 方法列表(Method List) 中动态注入新的项。

  • 添加实例方法与类方法

    这是最常见的用法,可以为系统的类(如 NSString)或自定义类添加工具方法。

  • 重写(覆盖)原类方法

    如果 Category 中的方法名与原类方法同名,Category 的方法会“覆盖”原类方法。

    原理: 运行时会将 Category 的方法插入到原类方法列表的前面。当消息分发查找方法时,会先找到 Category 的实现。

  • 添加属性(仅限于声明 Getter/Setter)

    你可以在 @interface 中声明 @property,但这不会自动生成成员变量(ivar),需要配合 Associated Objects(关联对象)来模拟实现。

  • 添加协议(Protocol)

    可以让一个现有的类通过 Category 遵循新的协议。

  • 私有方法公有化

    在 Category 中声明原类 .m 文件中未公开的方法,从而允许外部调用。

  • 分解大型类

    将一个庞大的类按功能拆分到多个 .m 文件中(如 User+Login.mUser+Profile.m),提高可维护性。


2. Category 不能做什么?

Category 的局限性源于 Objective-C 类的 编译期内存布局 是固定的。

  • 不能添加成员变量(Instance Variables / ivar)

    这是 Category 最核心的限制。类的 ivar 布局在编译时就已经确定,Category 发生在运行时,无法在已确定的内存块中“塞入”新的变量。

  • 不能自动合成属性(No @synthesize

    虽然能写 @property,但编译器不会为你自动生成 _variable 成员变量,也不会实现 Getter/Setter。

  • 无法通过 super 调用原类的同名实现

    一旦 Category 覆盖了原类的方法,原类的该方法依然存在但被“遮蔽”了。在 Category 实现内部,super 指向的是父类,而不是原类本身。

  • 多个 Category 冲突不可控

    如果多个 Category 实现了同一个方法,哪个会被执行取决于编译顺序(最后编译的生效)。这种不确定性是开发中的隐患。


3. Category vs Extension(类扩展)

很多人容易混淆这两者,它们的区别决定了它们的使用场景:

特性Category (分类)Extension (类扩展)
存在形式有独立的 .h.m通常写在原类的 .m 头部
添加 ivar不可以可以
生效时间运行时(Runtime)编译时(Compile-time)
源码要求不需要原类源码(可扩展系统类)必须有原类源码
主要用途模块化、扩展系统功能声明私有属性、私有方法

4. 绕过限制:Associated Objects

虽然 Category 不能直接添加成员变量,但我们可以利用运行时 API 来“模拟”属性存储。

Objective-C

#import <objc/runtime.h>

@implementation UIView (Theme)

static char kThemeColorKey; // 唯一 key

- (void)setThemeColor:(UIColor *)themeColor {
    // 将对象与 self 关联,模拟 ivar
    objc_setAssociatedObject(self, &kThemeColorKey, themeColor, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

- (UIColor *)themeColor {
    return objc_getAssociatedObject(self, &kThemeColorKey);
}

@end

💡 核心避坑指南

  1. 给 Category 方法加前缀:为了防止与原类或第三方库冲突,建议方法名为 prefix_methodName
  2. 谨慎重写方法:除非你有意拦截,否则重写原类方法可能导致系统原有逻辑失效,且难以追踪。