区别
分类
- 声明私有方法
- 分解体积庞大的类文件
- 把Framework的私有方法公开
- 可以添加实例方法、类方法、协议、属性
- 运行时决议
- 可以为系统类添加分类
- 在分类中定义属性,实际上是只声明了对应的
getter
和setter
方法,并没有添加实例变量
- 可以通过
关联对象
添加实例变量
- 在分类中定义属性,实际上是只声明了对应的
扩展
Category的加载处理过程
- 通过
Runtime
加载某个类的所有Category数据 - 把所有Category的方法、属性、协议数据,合并到一个大数组中 后面参与编译的Category数据,会在数组的前面
- 将合并后的分类数据(方法、属性、协议),插入到类原来数据的前面
总结
- 分类添加的方法可以
"覆盖"
原类方法 - 同名分类方法谁能生效取决于编译顺序
- 名字相同的分类会引起编译报错
+load方法
+load
方法会在runtime
加载类、分类时调用- 每个类、分类的
+load
,在程序运行过程中只调用一次
注意:+load方法是根据方法地址直接调用,并不是经过
objc_msgSend
函数调用
调用顺序
- 先调用类的+load
- 按照编译先后顺序调用(先编译,先调用)
- 调用子类的+load之前会先调用父类的+load
- 再调用分类的+load
- 按照编译先后顺序调用(先编译,先调用)
+initialize
方法
+initialize
方法会在类第一次接收到消息时调用+initialize
和+load
的很大区别是,+initialize
是通过objc_msgSend
进行调用的- 所以有以下特点 如果子类没有实现
+initialize
,会调用父类的+initialize
(所以父类的+initialize可能会被调用多次) - 如果分类实现了
+initialize
,就覆盖类本身的+initialize
调用
调用顺序
- 先调用父类的
+initialize
,再调用子类的+initialize
- (先初始化父类,再初始化子类,每个类只会初始化1次)
分类“添加成员变量”
默认情况下,因为分类底层结构的限制,不能添加成员变量到分类中。但可以通过关联对象来间接实现
关联对象提供了以下API
//添加关联对象
void objc_setAssociatedObject(id object, const void * key, id value, objc_AssociationPolicy policy)
//获得关联对象
id objc_getAssociatedObject(id object, const void * key)
//移除所有的关联对象
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
关联对象的原理
实现关联对象技术的核心对象有
- AssociationsManager
- AssociationsHashMap
- ObjectAssociationMap
- ObjcAssociation
objc4源码解读:objc-references.mm
- 关联对象由
AssociationsManager
管理并在AssociationsHashMap
存储 - 关联对象并不是存储在被关联对象本身内存中
- 关联对象存储在全局的统一的一个
AssociationsManager
中 - 设置关联对象为nil,就相当于是移除关联对象
面试题
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方法的区别什么?
- 调用方式
- load是根据函数地址直接调用
- initialize是通过objc_msgSend调用
- 调用时刻
- load是runtime加载类、分类的时候调用(只会调用1次)
- initialize是类第一次接收到消息的时候调用,每一个类只会initialize一次(父类的initialize方法可能会被调用多次)
load、initialize的调用顺序?
- load
- 先调用类的load
- 先编译的类,优先调用load
- 调用子类的load之前,会先调用父类的load
- 再调用分类的load
- 先编译的分类,优先调用load
- 先调用类的load
- initialize
- 不能直接给Category添加成员变量,但是可以间接实现Category有成员变量的效果