Objective-C 之 category(分类)

418 阅读5分钟

category 简介

category 是 Objective-C 2.0 之后添加的语言特性,category 的主要作用是为已经存在的类(不入侵原类的基础上)添加方法和属性。除此之外,通常 Category 有以下几种使用场景:。

  • 把类的实现分开在几个不同的文件里面。这样做的好处有:
    1. 减少单个文件的体积
    2. 把不同的功能组织到不同的 category 里
    3. 由多个开发者共同完成一个类
    4. 按需加载想要的 category 等。
  • 声明私有方法
  • 模拟多继承
  • 把 framework 的私有方法公开

category 和 extension

extension 很像一个匿名的 category,但是 extension 和有名字的 category 是两个不同东西。extension 在编译期决定,它就是类的一部分,在编译期和头文件里的 @interface 以及实现文件里的 @implement 一起形成一个完整的类,它伴随类的产生而产生,亦随之一起消亡。 extension 一般用来隐藏类的私有信息,你必须有一个类的源码才能为一个类添加 extension,所以你无法为系统的类添加 extension。

category 则完全不一样,它是在运行期决定的。 category 的特性是:可以在运行时阶段动态地为已有类添加新行为。extension 可以添加实例变量,而 category 是无法添加实例变量,是因为在运行期,对象的内存布局已经确定。

category 本质

OC 类和对象,在 runtime 层都是用结构体表示的,category 也不例外,在 objc/runtime.h 中,Category 被定义为 struct category_t,数据结构如下:

struct category_t {
    const char *name;                            // 类名
    classref_t cls;                              // 类,在运行时阶段通过 clasee_name(类名)对应到类对象
    struct method_list_t *instanceMethods;       // Category 中所有添加的对象方法列表
    struct method_list_t *classMethods;          // Category 中所有添加的类方法列表
    struct protocol_list_t *protocols;           // Category 中实现的所有协议列表
    struct property_list_t *instanceProperties;  // Category 中添加的所有属性
};
typedef struct category_t *Category;

从 struct category 的定义也可以看出 category 可以为类添加对象方法、类方法、协议、属性。无法添加成员变量。

Category 加载

Category 的方法、属性、协议只是添加到原有类上,并没有将原有类的方法、属性、协议进行完全替换。如果 category 和原来类都有methodA,那么category 加载完成之后,类的方法列表里会有两个 methodA。

Category 的方法、属性、协议会被添加到原有类的方法列表、属性列表、协议列表的最前面,而原有类的方法、属性、协议则被移动到了列表后面。这也就是我们平常所说的 category 的方法会“覆盖”掉原来类的同名方法,这是因为运行时在查找方法的时候是顺着方法列表的顺序查找的,所以 Category 的方法会先被搜索到,然后直接执行,而原有类的方法则不被执行。

category 和 +load 方法

Category 附加到类上的操作,在 + load 方法执行之前。即,在 + load 方法执行之前,类中就已经加载了 Category(分类)中的的方法、属性、协议。

Category 和 Class 的 + load 方法执行顺序是先类,后 category,而 category 的 +load 执行顺序是根据编译顺序决定的。

category 和关联对象

我们知道在 Category 中虽然可以添加属性,但是不会生成对应的成员变量,也不能生成 gettersetter 方法实现。因此,在调用 Category 中声明的属性时会报错。可以实现 gettersetter 方法,并借助关联对象来实现 gettersetter 方法。关联对象能够帮助我们在运行时阶段将任意的属性关联到一个对象上

// 1. 通过 key : value 的形式给对象 object 设置关联属性
void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy);
// 2. 通过 key 获取关联的属性 object
id objc_getAssociatedObject(id object, const void *key);
// 3. 移除对象所关联的属性
void objc_removeAssociatedObjects(id object);

示例:

@interface UIImage (Url)

@property (nonatomic, copy) NSString *urlString;
// 用于清除关联对象
- (void)clearAssociatedObjcet;
@end
#import "UIImage+Url.h"
#import <objc/runtime.h>

@implementation UIImage (Url.h)
// set 方法
- (void)setUrlString:(NSString *)urlString {
    objc_setAssociatedObject(self, @selector(urlString), urlString, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
// get 方法
- (NSString *)urlString {
    return objc_getAssociatedObject(self, @selector(urlString));
}
// 清除关联对象
- (void)clearAssociatedObjcet {
    objc_removeAssociatedObjects(self);
}
@end

借助关联对象,我们成功的在 UIImage 分类中为 UImage 类增加了 urlString 关联属性,并实现了 gettersetter 方法。,关联对象都由AssociationsManager管理

category 、inherit(继承) 的优缺点?

优点:

  1. category 在不入侵原类的基础上添加方法和属性,比继承更为轻量级。
  2. category 可以将类的实现分散到多个文件。
  3. category 可以创建私有方法的前向引用:在类别里提供一个类的私有方法的声明(不需要提供实现),当实例调用的时候,编译器不会报错,运行时会调用类的私有方法,继承不能继承私有方法的实现。
  4. category 可以向类添加非正式协议:因为 Object-C 里面所有类都是 NSObject 的子类,所以 NSObject 的类别里定义的函数,所有对象都能使用。 缺点:
  5. category 不能添加实例变量。
  6. category 定义的方法如果和类里面的方法同名,则会覆盖原来类定义的方法。
  7. 如果一个类有多个 category,每个 category 定义了同一个方法,则类调用的方法是哪个category的是不确定的。