区别

分类

  • 声明私有方法
  • 分解体积庞大的类文件
  • 把Framework的私有方法公开
  • 可以添加实例方法、类方法、协议、属性
  • 运行时决议
  • 可以为系统类添加分类
    • 在分类中定义属性,实际上是只声明了对应的gettersetter方法,并没有添加实例变量
    • 可以通过关联对象添加实例变量

扩展

  • 声明私有属性、方法、成员变量
  • 编译时决议
  • 只以声明的形式存在,多数情况下寄生在宿主类的.m
  • 不能为系统类添加扩展

    Category的底层结构

  • 定义在objc-runtime-new.h

image.png

Category的加载处理过程

  • 通过Runtime加载某个类的所有Category数据
  • 把所有Category的方法、属性、协议数据,合并到一个大数组中 后面参与编译的Category数据,会在数组的前面
  • 将合并后的分类数据(方法、属性、协议),插入到类原来数据的前面

image.png

总结

  • 分类添加的方法可以"覆盖"原类方法
  • 同名分类方法谁能生效取决于编译顺序
  • 名字相同的分类会引起编译报错

+load方法

  • +load方法会在runtime加载类、分类时调用
  • 每个类、分类的+load,在程序运行过程中只调用一次

image.png

注意:+load方法是根据方法地址直接调用,并不是经过objc_msgSend函数调用

调用顺序

  • 先调用类的+load
    • 按照编译先后顺序调用(先编译,先调用)
    • 调用子类的+load之前会先调用父类的+load
  • 再调用分类的+load
    • 按照编译先后顺序调用(先编译,先调用)

+initialize方法

  • +initialize方法会在类第一次接收到消息时调用
  • +initialize+load的很大区别是,
    • +initialize是通过objc_msgSend进行调用的
    • 所以有以下特点 如果子类没有实现+initialize,会调用父类的+initialize(所以父类的+initialize可能会被调用多次)
    • 如果分类实现了+initialize,就覆盖类本身的+initialize调用

image.png

调用顺序

  • 先调用父类的+initialize,再调用子类的+initialize
  • (先初始化父类,再初始化子类,每个类只会初始化1次)

分类“添加成员变量”

默认情况下,因为分类底层结构的限制,不能添加成员变量到分类中。但可以通过关联对象来间接实现

关联对象提供了以下API

  1. //添加关联对象
  2. void objc_setAssociatedObject(id object, const void * key, id value, objc_AssociationPolicy policy)
  3. //获得关联对象
  4. id objc_getAssociatedObject(id object, const void * key)
  5. //移除所有的关联对象
  6. void objc_removeAssociatedObjects(id object)

key的常见用法

static void *MyKey = &MyKey;
objc_setAssociatedObject(obj, MyKey, value, OBJC_ASSOCIATION_RETAIN_NONATOMIC)
objc_getAssociatedObject(obj, MyKey)

static char MyKey;
objc_setAssociatedObject(obj, &MyKey, value, OBJC_ASSOCIATION_RETAIN_NONATOMIC)
objc_getAssociatedObject(obj, &MyKey)

使用属性名作为key
objc_setAssociatedObject(obj, @"property", value, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
objc_getAssociatedObject(obj, @"property");

使用get方法的@selecor作为key
objc_setAssociatedObject(obj, @selector(getter), value, OBJC_ASSOCIATION_RETAIN_NONATOMIC)
objc_getAssociatedObject(obj, @selector(getter))

objc_AssociationPolicy

image.png

关联对象的原理

实现关联对象技术的核心对象有

  • AssociationsManager
  • AssociationsHashMap
  • ObjectAssociationMap
  • ObjcAssociation

objc4源码解读:objc-references.mm
image.png

  • 关联对象由AssociationsManager管理并在 AssociationsHashMap 存储
  • 关联对象并不是存储在被关联对象本身内存中
  • 关联对象存储在全局的统一的一个AssociationsManager
  • 设置关联对象为nil,就相当于是移除关联对象

image.png
image.png
image.png

面试题

Category的使用场合是什么?

Category的实现原理

  • Category编译之后的底层结构是struct category_t,里面存储着分类的对象方法、类方法、属性、协议信息
  • 在程序运行的时候,runtime会将Category的数据,合并到类信息中(类对象、元类对象中)

Category和Class Extension的区别是什么?

  • Class Extension在编译的时候,它的数据就已经包含在类信息中
  • Category是在运行时,才会将数据合并到类信息中

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

  • 有load方法
  • load方法在runtime加载类、分类的时候调用
  • load方法可以继承,但是一般情况下不会主动去调用load方法,都是让系统自动调用

load、initialize方法的区别什么?

  1. 调用方式
    • load是根据函数地址直接调用
    • initialize是通过objc_msgSend调用
  2. 调用时刻
    • load是runtime加载类、分类的时候调用(只会调用1次)
    • initialize是类第一次接收到消息的时候调用,每一个类只会initialize一次(父类的initialize方法可能会被调用多次)

load、initialize的调用顺序?

  1. load
    • 先调用类的load
      • 先编译的类,优先调用load
      • 调用子类的load之前,会先调用父类的load
    • 再调用分类的load
      • 先编译的分类,优先调用load
  2. initialize
    • 先初始化父类
    • 再初始化子类(可能最终调用的是父类的initialize方法)

      Category能否添加成员变量?如果可以,如何给Category添加成员变量?

  • 不能直接给Category添加成员变量,但是可以间接实现Category有成员变量的效果