iOS基础分类和扩展(Category&&Extension)

问题

分类和扩展有什么区别?可以分别来做什么?分类有哪些局限性?分类的结构体里面有哪些成员?

分类的概念

Category 产生于 Objective-C 2.0,它的主要作用是为已经存在的类添加方法。它的使用场景:

比如说,我需要在当前控制器下处理一个字符串,写好处理方法,直接调用;但是,我别的类可能也需要调用该方法,我可以创建一个工具类,将该方法写在工具类中,也可以使用分类,为这个类添加额外的方法。

category 还有一些其他用法:

  • 将类的实现分开写在几个分类里面(比如说为AppDelegate添加category)
    • 可以减少耽搁文件的体积
    • 可以把不同的功能组织到不同的category里
    • 可以由多个开发者共同完成一个类
    • 可以按需加载想要的category

所有的OC类和对象,在runtime层都是用struct表示的,Category 在runtime层用 category_t 表示(objc-runtime-new.h中可以找到其定义)。

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;
};
  1. name: 类的名字
  2. cls: 类
  3. instanceMethods: category中所有给类添加的实例方法的列表
  4. classMethods: category中所有给类添加的类方法的列表
  5. protocols: category实现的所有协议的列表
  6. instanceProperties: category中添加的所有属性

从category的定义中可以看出,category可以添加类方法、实例方法、属性甚至可以实现协议。但是,不能添加实例变量。

category 和 extension 类比

  • extension 和 category 好像很像,但是完全不是一个东西。
    extension 其实我们开发过程中经常再用,人们总是往往忽略身边的东西,我们经常会在.m 文件中写 @interface 这个东西,往往我们用来隐藏私有信息。它是在编译期决议的。它伴随着类的产生,以及类的消亡。你必须有一个类的源码,你才可以为一个类添加 extension。所以你无法为系统的类添加 extension。

  • category 是在运行期决议的。extension可以添加实例变量,而category是不可以的(因为在运行期,对象的内部布局已经确定了。如果添加实例变量就会破坏类的内存布局)-- 为什么不能添加成员变量

分类使用注意

  • 分类可以“覆盖”原来类中的同名方法,会导致原来类中的方法无法使用。(实际上并没有真正的覆盖,而是category的方法放在了新方法列表的最前面,而原来的方法放在方法列表的后面,这是因为运行时在查找方法的时候是顺着方法列表的顺序查找的,它只要一找到对应的方法,就会停止查找)-- 为什么我们建议在分类的方法前添加前缀
  • 当分类、原类、原类的父类有相同的方法的时候,方法调用的优先级如下:分类 > 原类 > 父类
  • category能不能添加属性?通过category的结构体可以看出,category的内存布局已经确定了,category可以添加属性,但是不会生成 成员变量以及setter/getter 方法。如果你在category文件中创建一个属性,当你在其他文件中调用时,会告诉你,找不到该方法的崩溃。当然,可以通过 runtime 动态生成 setter/getter 方法。但是无法生成成员变量。

具体为什么不能生成成员变量?

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 结构体中,ivars 是 objc_ivar_list(成员变量列表)的指针, methodLists 是指向 objc_method_list指针的指针。在runtime 中,objc_class 结构体大小是固定的,不可能往这里添加数据,只能修改。所以,ivars 指向了一个固定区域,只能修改成员变量的值,不能增加成员变量的个数。方法列表是一个二维数组,可以修改 *methodLists的值来增加成员方法,虽然没办法扩展methodLists指向的内存区域,却可以改变这个内存区域的值(里面存的是指针),因此,可以动态添加方法,不可以添加成员变量。

你可能感兴趣的:(iOS基础分类和扩展(Category&&Extension))