iOS 分类与扩展

1,473 阅读5分钟

1,分类(category)

1.1 我们用分类经常做什么事情呢?

1,用分类声明私有方法

2,分解体积庞大的文件

3,把framework文件的私有方法公开

1.2 分类的特点

1,分类是运行时决议

2,可以为系统类添加分类

1.3 分类可以添加哪些内容

1,添加实例方法

2,添加类方法

3,添加协议

4,添加属性

1.4 分类的底层结构

分类的底层结构我们可以通过runtime源码的category_t 结构体进行查看

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; // 属性
    // Fields below this point are not always present on disk.
    struct property_list_t *_classProperties;

    method_list_t *methodsForMeta(bool isMeta) {
        if (isMeta) return classMethods;
        else return instanceMethods;
    }

    property_list_t *propertiesForMeta(bool isMeta, struct header_info *hi);
};

从源码的category_t的结构体中,我们可以看到:

1,已经有对象方法,类方法,协议方法和属性的存储方式。但是并不存在成员变量,所以分类是不允许添加成员变量的。

2, 分类的属性是不会自动生产成员变量,set/get方法。我们可以通过runtime动态的生产set/get方法

1.5 分类总结

1,Category的实现原理

答:分类的实现原理是将category的方法,属性,协议数据放在category_t的结构体中,然后将结构体内的方法列表拷贝到对象的方法列表中。category可以添加属性,但是并不会自动生成成员变量及set/get方法,所以category_t结构体中并不会存在成员变量。

2, Category为什么只能加方法不能加属性

答:通过之前对对象的分析我们知道成员变量是存放在实例对象中的,并且编译的那一刻就已经决定好了。而分类是在运行时才去加载的。那么我们就无法再程序运行时将分类的成员变量中添加到实例对象的结构体中。因此分类中不可以添加成员变量。

3:总结

1,分类添加的方法会“覆盖”原类的方法

2,同名的分类方法谁能生效取决于编译顺序(最后编译的会生效)

3,名字相同的分类会引起编译报错

1.6 分类的面试题总结

1,Category中有load方法吗?load方法是什么时候调用的?load 方法能继承吗?

答:category中有load方法,load方法在程序启动装载类信息的时候就会调用。load方法可以继承。调用子类load方法之前,会先调用父类的load方法。

2,load、initialize的区别,以及它们在category重写的时候的调用的次序。

答:1,区别:在于调用方式和调用时刻 

调用方式:load是根据函数地址直接调用,initialize是通过objc_msgSend调用 

调用时刻:load是runtime加载类、分类的时候调用(只会调用1次),initialize是类第一次接收到消息的时候调用,每一个类只会initialize一次(父类的initialize方法可能会被调用多次)

2,调用顺序先调用类的load方法,先编译那个类,就先调用load。在调用load之前会先调用父类的load方法。分类中load方法不会覆盖本类的load方法,先编译的分类优先调用load方法。

initialize先初始化父类,之后再初始化子类。如果子类没有实现+initialize,会调用父类的+initialize(所以父类的+initialize可能会被调用多次),如果分类实现了+initialize,就覆盖类本身的+initialize调用。

参考链接:https://juejin.cn/post/6844903602524274696

2,扩展(Extension)

Extension是Category的一个特例。类扩展与分类相比只少了分类的名称,所以称之为“匿名分类”。 其实开发当中,我们几乎天天在使用。对于有些人来说像是最熟悉的陌生人。在我们平常的 .m 文件中的 @interface XXX() 到 @end 这部分就属于这个类的扩展。

2.1 类扩展的格式

@interface XXX ()

//私有属性
//私有方法(如果不实现,编译时会报警告,Method definition for 'XXX' not found)

@end

2.2 类扩展的说明

  • 为一个类添加额外的原来没有变量,方法和属性
  • 一般的类扩展写到.m文件中
  • 一般的私有属性写到.m文件中的类扩展 

3 分类与类扩展的区别

1,分类中原则上只能增加方法(能添加属性的的原因只是通过runtime解决无setter/getter的问题而已);

2,类扩展不仅可以增加方法,还可以增加实例变量(或者属性),只是该实例变量默认是@private类型的( 作用范围只能在自身类,而不是子类或其他地方);

3,类扩展中声明的方法没被实现,编译器会报警告,但是分类中的方法没被实现编译器是不会有任何警告的。这是因为类扩展是在编译阶段被添加到类中,而分类是在运行时添加到类中。

4,类扩展不能像分类那样拥有独立的实现部分(@implementation部分),也就是说,类扩展所声明的方法必须依托对应类的实现部分来实现。

5,定义在 .m 文件中的类扩展方法为私有的,定义在 .h 文件(头文件)中的类扩展方法为公有的。类扩展是在 .m 文件中声明私有方法的非常好的方式。