Category VS Extension 原理详解

Category VS Extension 原理详解

深入理解Objective-C:Category


一: category

categoryObjective-C 2.0之后添加的语言特性,别人口中的 分类、类别其实都是指的categorycategory的主要作用是为已经存在的类添加方法。除此之外,apple还推荐了category的另外两个使用场景。

1.1: 什么是Category

可以把类的实现分开在几个不同的文件里面。这样做有几个显而易见的好处。

  • 可以减少单个文件的体积
  • 可以把不同的功能组织到不同的category里
  • 可以由多个开发者共同完成一个类
  • 可以按需加载想要的category
  • 声明私有方法

apple的SDK中就大面积的使用了category这一特性。比如UIKit中的UIView。apple把不同的功能API进行了分类,这些分类包括UIViewGeometry、UIViewHierarchy、UIViewRendering等。

不过除了apple推荐的使用场景,广大开发者脑洞大开,还衍生出了category的其他几个使用场景:

  • 模拟多继承(另外可以模拟多继承的还有protocol)
  • 把framework的私有方法公开
1.2: category特点
  • category只能给某个已有的类扩充方法,不能扩充成员变量。
  • category中也可以添加属性,只不过@property只会生成settergetter的声明,不会生成settergetter的实现以及成员变量。
  • 如果category中的方法和类中原有方法同名,运行时会优先调用category中的方法。也就是,category中的方法会覆盖掉类中原有的方法。所以开发中尽量保证不要让分类中的方法和原有类中的方法名相同。避免出现这种情况的解决方案是给分类的方法名统一添加前缀。比如category_
  • 如果多个category中存在同名的方法,运行时到底调用哪个方法由编译器决定,最后一个参与编译的方法会被调用。

如下图,给UIView添加了两个categoryonetwo),并且给这两个分类都添加了名为log的方法:

Category VS Extension 原理详解_第1张图片
UIView+one
Category VS Extension 原理详解_第2张图片
UIView+two

在viewController中引入这两个category的.h文件。调用log方法:

调用category方法

当编译顺序如下图所示时,调用UIView + one.m的log方法,如下图:

Category VS Extension 原理详解_第3张图片
编译顺序

Category VS Extension 原理详解_第4张图片
调用结果

UIView + one.m移动到 UIView + two.m上面,调用 UIView + two.m的log方法,如下图:

Category VS Extension 原理详解_第5张图片
编译顺序
Category VS Extension 原理详解_第6张图片
调用结果
1.3: 调用优先级

分类(category) > 本类 > 父类。即,优先调用cateory中的方法,然后调用本类方法,最后调用父类方法。

注意:category是在运行时加载的,不是在编译时。

1.4: 为什么category不能添加成员变量?

Objective-C类是由Class类型来表示的,它实际上是一个指向objc_class结构体的指针。它的定义如下:

typedef struct objc_class *Class;

objc_class结构体的定义如下:

struct objc_class {
    Class isa  OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
    Class super_class                       OBJC2_UNAVAILABLE;  // 父类
    const char *name                        OBJC2_UNAVAILABLE;  // 类名
    long version                            OBJC2_UNAVAILABLE;  // 类的版本信息,默认为0
    long info                               OBJC2_UNAVAILABLE;  // 类信息,供运行期使用的一些位标识
    long instance_size                      OBJC2_UNAVAILABLE;  // 该类的实例变量大小
    struct objc_ivar_list *ivars            OBJC2_UNAVAILABLE;  // 该类的成员变量链表
    struct objc_method_list **methodLists   OBJC2_UNAVAILABLE;  // 方法定义的链表
    struct objc_cache *cache                OBJC2_UNAVAILABLE;  // 方法缓存
    struct objc_protocol_list *protocols    OBJC2_UNAVAILABLE;  // 协议链表
#endif
} OBJC2_UNAVAILABLE;

在上面的objc_class结构体中,ivarsobjc_ivar_list(成员变量列表)指针;methodLists是指向objc_method_list指针的指针。在Runtime中,objc_class结构体大小是固定的,不可能往这个结构体中添加数据,只能修改。所以ivars指向的是一个固定区域,只能修改成员变量值,不能增加成员变量个数。methodList是一个二维数组,所以可以修改*methodLists的值来增加成员方法,虽没办法扩展methodLists指向的内存区域,却可以改变这个内存区域的值(存储的是指针)。因此,可以动态添加方法,不能添加成员变量。

你可能感兴趣的:(Category VS Extension 原理详解)