一、load 方法
1、load方法加载
- +Load方法会在runtime加载类、分类时调用
- 每个类、分类的+load方法,在程序运行过程中只调用一次。
2、调用顺序
-
先调用类的+load方法
- 按照编译先后顺序调用(先编译,先调用)
- 调用子类的+load方法之前会先调用父类的+load方法
-
再调用分类的+load方法
- 按照编译先后顺序调用(先编译先调用)
3、load加载过程底层原发源码执行顺序
_objc_init: 加载对象的入口方法,_objc_init里面调用load_images方法
load_images :加载所有类load的方法,里面调用prepare_load_methods、call_load_methods.
-
prepare_load_methods:加载load方法的准备工作
- classref_t *classList = _getObjc2NonlazyClassList(mhdr, &count)方法获取所有类,遍历classList
- 遍历classList,调用schedule_class_load(class cls):
- 先 递归调用schedule_class_load(cls->superclass)调用父类
- add_class_to_loadable_list:讲cls加载到数据的后面
- 遍历classList,调用schedule_class_load(class cls):
- category **categoryList = _getObjc2NonlazyCategoryList(mhdr, &count);获取所有分类。
- 遍历categoryList,调用add_category_to_loadable_list把分类加到列表最后面。
- classref_t *classList = _getObjc2NonlazyClassList(mhdr, &count)方法获取所有类,遍历classList
(categoryList是一个二维数组里,第一维代表是某一个类,第二维代表的是某个类下面有多个分类。
[person_category_list,stu_categorylist].
person_category_list = [category_t, category_t]
stu_category_list = [category_t, category_t]
- call_load_methods:调用load方法列表
-
call_class_loads():先加载完类的load方法
- 类的加载过程,遍历add_class_to_loadable_list中类,调用load_method函数调用load方法。
-
call_category_loads():在加载分类的load方法
- 分类的加载过程,遍历add_class_to_loadable_list中类,调用load_method函数调用load方法。
-
类和分类最后都会调用(*load_method)(cls, SEL_load) 这个函数,+load方法是根据方法地址直接调用,并不是经过objc_msgSend函数调用。
二、initialize方法
一、initialize调用时机
-
+initialize 方法会在类第一次收到消息时调用
- 先调用父类的+initlialize,在调用子类的+initialize。
二、底层源码实现
- 调用objc_msgSend
class_getClassMethod(Class cls,SEL sel)(找类方法,传的就是元类)
lookUpImpOrNil 找方法
lookUpImpOrForward 继续查找
if (initialize && !cls->isInitialize) {
_class_initialize方法调用initialize
} 先判断是否需要初始化,需要的话就调用callInitialize调用initialize
最后调用到 objc_msgSend(cls, SEL_initialize)
三.load 与initialize区别
1、调用方式
- load是根据函数地址调用
- initialize是通过objc_msgSend调用
2、调用时刻
- load是runtime加载类、分类的时候调用(只会调用一次)
- initialize是类第一次接收到消息的时候调用,每一个类只会initialize一次(父类的initialize方法可能会被调用多次,当多个子类没有自己实现initialize,会调用父类)
3、l调用顺序
load调用顺序:
-
先调用类的load
- 先编译的类,优先调用load
- 调用子类的load之前,会先调用父类的load
-
在调用分类的load
- 先编译的分类,优先调用load
initialize调用顺序
- 先初始化父类
- 再初始化子类(子类没实现,最终调用的是父类的initialize方法)
三、关联对象
给分类添加属性,分类只声明没有方法的实现,实现的方式是关联对象。
关联对象所用的相关类主要是下面4个类:
- AssociationManager
- AssociationHashMap
- ObjectAssociationMap
- ObjcAssociation
使用方式
分类.h文件
@property(nonatomic,copy)NSString *name;
分类.m文件
- (void)setName {
objc_setAssociatedObject( self,
@slector(name), OBJC_ASSOCIATION_COPY_NONATOMIC)
}
- (void)name {
objc_getAssociatedObject( self,
@slector(name), OBJC_ASSOCIATION_COPY_NONATOMIC)
}
第一个参数给关联对象
第二个是key
第三参数是关联策略
-
objc_AssociationPolicy :对应的修饰符
- OBJC_ASSOCIATION_ASSIGN :assign
- OBJC_ASSOCIATION_RETAIN_NONATOMIC: strong, nonatomic
- OBJC_ASSOCIATION_COPY_NONATOMIC: copy, nonatomic
- OBJC_ASSOCIATION_RETAIN:strong, atomic
- OBJC_ASSOCIATION_COPY :copy, atomic
关联对象的核心原理
- AssociationManager中声明一个全局的haspmap。
class AssociationManager {
static AssociationHashMap *_map;
}
- AssociationHashMap中存储着键值对,key是这个关联的object,值是Object AssociationMap这个也是对象的hasmap
AssociationHashMap : public unordered_map<disguised_prt_t, Object AssociationMap>
如下面表格:
key | value |
---|---|
disguised_prt_t | Object AssociationMap |
disguised_prt_t | Object AssociationMap |
- AssociationMap这个也是一个存储着键值对的hashmap
class ObjectAssociationMap : public std ::map <void *, ObjcAssociation>
如下面的表格:
key | value |
---|---|
void * | ObjcAssociation |
void * | ObjcAssociation |
- ObjcAssociation 包含关联策略和值,如下:
class ObjcAssociation {
uintptr_t _policy;
id _value;
}
关联对象的原理执行流程如下图:
[图片上传失败...(image-212701-1620575201542)]
3、移除关联对象
- 设置关联对象为nil,就相当于移除关联对象
- remove_Association(self) 移除该对象所有的挂链对象
4.关联对象总结
- 关联对象并不是存储在关联对象本身内存中
- 关联对象存储在全局同意的一个AssociationsManager中
四、KVO
1、kvo 使用
- 注册观察者
/*
@observer:就是观察者,是谁想要观测对象的值的改变。
@keyPath:就是想要观察的对象属性。
@options:options一般选择NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld,
这样当属性值发生改变时我们可以同时获得旧值和新值,如果我们只填NSKeyValueObservingOptionNew则属性发生改变时只会获得新值。
@context:想要携带的其他信息,比如一个字符串或者字典什么的。
*/
- (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath
options:(NSKeyValueObservingOptions)options context:(nullable void *)context;
- 监听回调
/*
@keyPath:观察的属性
@object:观察的是哪个对象的属性
@change:这是一个字典类型的值,通过键值对显示新的属性值和旧的属性值
@context:上面添加观察者时携带的信息
*/
- (void)observeValueForKeyPath:(nullable NSString *)keyPath
ofObject:(nullable id)object change:(nullable NSDictionary<NSKeyValueChangeKey, id> *)change
context:(nullable void *)context;
- kvo 原理
如下图:
五、KVC实现原理
- setValue: forKey
执行流程如下图:
- valueForKey
执行流程如下图: