本页所使用的objc runtime 756.2,来自 Apple 开源文档
类的加载探寻系列:
1、【类的加载】-(1)类的启动
一、加载镜像load_images
前两篇文章学习了objc_init 中的主要方法map_images
,接下来学习加载镜像,先把源码摆出来:
通过注视可以看到,主要执行了2件事务:
- 找到 load 方法
- 调用load 方法。
void |
其中找到load 方法,是通过先找到其背后的类或者分类。
1.1 准备类
下面这行代码表示类
void prepare_load_methods(const headerType *mhdr) |
找到类的列表:
classref_t *classlist =
_getObjc2NonlazyClassList(mhdr, &count);准备load 方法,把类添加到可load 列表,将类标记为可执行
static void schedule_class_load(Class cls)
{
if (!cls) return;
assert(cls->isRealized()); // _read_images should realize
if (cls->data()->flags & RW_LOADED) return;
// Ensure superclass-first ordering
schedule_class_load(cls->superclass);
add_class_to_loadable_list(cls);
cls->setInfo(RW_LOADED);
}把类添加到可加载列表的实现
void add_class_to_loadable_list(Class cls)
{
IMP method;
loadMethodLock.assertLocked();
method = cls->getLoadMethod();
if (!method) return; // Don't bother if cls has no +load method
if (PrintLoading) {
_objc_inform("LOAD: class '%s' scheduled for +load",
cls->nameForLogging());
}
if (loadable_classes_used == loadable_classes_allocated) {
loadable_classes_allocated = loadable_classes_allocated*2 + 16;
loadable_classes = (struct loadable_class *)
realloc(loadable_classes,
loadable_classes_allocated *
sizeof(struct loadable_class));
}
loadable_classes[loadable_classes_used].cls = cls;
loadable_classes[loadable_classes_used].method = method;
loadable_classes_used++;
}把分类添加到可加载列表
void add_category_to_loadable_list(Category cat)
{
IMP method;
loadMethodLock.assertLocked();
method = _category_getLoadMethod(cat);
// Don't bother if cat has no +load method
if (!method) return;
if (PrintLoading) {
_objc_inform("LOAD: category '%s(%s)' scheduled for +load",
_category_getClassName(cat), _category_getName(cat));
}
if (loadable_categories_used == loadable_categories_allocated) {
loadable_categories_allocated = loadable_categories_allocated*2 + 16;
loadable_categories = (struct loadable_category *)
realloc(loadable_categories,
loadable_categories_allocated *
sizeof(struct loadable_category));
}
loadable_categories[loadable_categories_used].cat = cat;
loadable_categories[loadable_categories_used].method = method;
loadable_categories_used++;
}其中
Class cls = remapClass(cat->cls);
这行代码,重映射了分类的类,即帮分类把懒加载的类进行了实现,否则分类无法找到可以依附的主类。
1.2 调用 load 方法
源码如下:
do { |
1.2.1 调用类的load 方法
- 列举可加载的类
- 循环找到类的对应load 方法的method 函数
- 向类发送该method 函数,完成调用
static void call_class_loads(void) |
1.2.2 调用分类的load 方法
列举可加载的分类
// Detach current loadable list.
struct loadable_category *cats = loadable_categories;
int used = loadable_categories_used;
int allocated = loadable_categories_allocated;
loadable_categories = nil;
loadable_categories_allocated = 0;
loadable_categories_used = 0;循环找到分类的对应load 方法的method 函数
// Call all +loads for the detached list.
for (i = 0; i < used; i++) {
Category cat = cats[i].cat;
load_method_t load_method = (load_method_t)cats[i].method;
}向分类发送该method 函数,完成调用
(*load_method)(cls, SEL_load);
释放可执行分类列表,保证程序启动只调用一次该加载。
这里通过一个叫做
loadable_categories_used
和used
的标识,来决定是否加载,并进行摧毁。把使用过的
int used = loadable_categories_used;
/****/
shift = 0;
for (i = 0; i < used; i++) {
if (cats[i].cat) {
cats[i-shift] = cats[i];
} else {
shift++;
}
}
used -= shift;
// Destroy the new list.
if (loadable_categories) free(loadable_categories);
二、类的扩展-extension
扩展的特性如下
- 作为匿名的分类
- 可以添加属性和方法
- 生成时间:编译时作为类的一部分(ro)一起被编译
- 如果有与主类同名扩展方法,会先执行扩展方法,因为attachList 内存前插,所以造成覆盖原方法的假象。
三、runtime 关联对象
3.1 介绍
3.1.1 概念
associatedObject又称关联对象。顾名思义,就是把一个对象关联到另外一个对象身上。
3.1.2 应用场景
为分类添加属性时,用到添加setter和getter方法,在实现里需要将类与属性关联
给某个类添加一个临时的属性
3.2 关联对象的使用
添加关联。
主要用到
void objc_setAssociatedObject
函数,源码的实现是:
void objc_setAssociatedObject(id object,
const void *key,
id value,
objc_AssociationPolicy policy) {
_object_set_associative_reference(object, (void *)key, value, policy);
}参数一:id object : 给哪个对象添加属性,这里要给自己添加属性,用self。
参数二:void * == id key : 属性名,根据key获取关联对象的属性的值,在objc_getAssociatedObject中通过次key获得属性的值并返回。
参数三:id value : 关联的值,也就是set方法传入的值给属性去保存。
参数四:objc_AssociationPolicy policy : 策略,属性以什么形式保存。
业务上,比如给当前的类把cate_name 作为属性绑定,业务代码如下
-(void)setCate_name:(NSString *)cate_name{
objc_setAssociatedObject(self, @"name",cate_name, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}获取关联属性
主要函数为:
objc_getAssociatedObject
方法查看源码的实现是:
id objc_getAssociatedObject(id object, const void *key) {
return _object_get_associative_reference(object, (void *)key);
}这里两个参数:
参数一:id object : 获取哪个对象里面的关联的属性。
参数二:void * == id key : 什么属性,与objc_setAssociatedObject中的key相对应,即通过key值取出value。
业务实现:去除当前类中 name属性,作为cate_name 返回
-(NSString *)cate_name{
return objc_getAssociatedObject(self, @"name");
}
3.1 关联对象关联原理
- 程序运行时创建一个大大Hash 表
- 通过manager 进入迭代器对每个类表查找
- 如果找到,对新添加的属性进行绑定
- 如果找不到,创建一个,进行绑定存储
通过manager 管理
// 关联对象的管理类
AssociationsManager manager;获取关联的 HashMap -> 存储当前关联对象
AssociationsHashMap &associations(manager.associations());
获取 AssociationsHashMap 的迭代器 - (对象的) 进行遍历
AssociationsHashMap::iterator i = associations.find(disguised_object); |
根据key去获取关联属性的迭代器
// 根据key去获取关联属性的迭代器
ObjectAssociationMap::iterator j = refs->find(key);
if (j != refs->end()) {
old_association = j->second;
// 替换设置新值
j->second = ObjcAssociation(policy, new_value);
} else {
// 到最后了 - 直接设置新值
(*refs)[key] = ObjcAssociation(policy, new_value);
}没有对象的关联信息情况。创建map,通过key-value 存入
if (new_value) {
/** 存在该对象,查找遍历,略过*/
}else{
ObjectAssociationMap *refs = new ObjectAssociationMap;
associations[disguised_object] = refs;
(*refs)[key] = ObjcAssociation(policy, new_value);
object->setHasAssociatedObjects();
}
3.2 关联对象查找原理
主要有以下几个步骤:
生成关联对象的管理类
AssociationsManager manager;
AssociationsHashMap &associations(manager.associations());生成伪装地址。处理参数 object 地址
disguised_ptr_t disguised_object = DISGUISE(object);
生成Hash表中所有对象的额迭代器
AssociationsHashMap::iterator i = associations.find(disguised_object);
对象内部继续生称迭代器,准备迭代属性
if (i != associations.end()) {
ObjectAssociationMap *refs = i->second;
// 内部对象的迭代器
ObjectAssociationMap::iterator j = refs->find(key);
}查找——找到 - 把值和策略读取出来
// 内部对象的迭代器
ObjectAssociationMap::iterator j = refs->find(key);
if (j != refs->end()) {
// 找到 - 把值和策略读取出来
ObjcAssociation &entry = j->second;
value = entry.value();
policy = entry.policy();
// OBJC_ASSOCIATION_GETTER_RETAIN - 就会持有一下
if (policy & OBJC_ASSOCIATION_GETTER_RETAIN) {
objc_retain(value);
}
}如果找到,就进行持有
if (policy & OBJC_ASSOCIATION_GETTER_RETAIN) {
objc_retain(value);
}
完整的查找过程的源码如下:
id _object_get_associative_reference(id object, void *key) { |
3.3 实战
3.3.1 目标
给UIAlertController
添加一个block 回调
3.3.2 添加前
添加钱,每个UIAlertAction 处理自己的逻辑,如果逻辑较多,业务分离开,不方便阅读。
static void *SIGNALERTCONTROLLER = "SIGNALERTCONTROLLER"; |
3.3.3 添加后
统一在block 中处理业务,逻辑更集中
static void *SIGNALERTCONTROLLER = "SIGNALERTCONTROLLER"; |
四、initialize 的调用
调用步骤如下
lookUpImpOrForward
if (initialize && !cls->isInitialized()) {
cls = initializeAndLeaveLocked(cls, inst, runtimeLock);
// runtimeLock may have been dropped but is now locked again
// If sel == initialize, class_initialize will send +initialize and
// then the messenger will send +initialize again after this
// procedure finishes. Of course, if this is not being called
// from the messenger then it won't happen. 2778172
}initializeAndLeaveLocked
// Locking: caller must hold runtimeLock; this may drop and re-acquire it
static Class initializeAndLeaveLocked(Class cls, id obj, mutex_t& lock)
{
return initializeAndMaybeRelock(cls, obj, lock, true);
}initializeAndMaybeRelock
// runtimeLock is now unlocked, for +initialize dispatch
assert(nonmeta->isRealized());
initializeNonMetaClass(nonmeta);initializeNonMetaClass
{
callInitialize(cls);
if (PrintInitializing) {
_objc_inform("INITIALIZE: thread %p: finished +[%s initialize]",
pthread_self(), cls->nameForLogging());
}
}callInitialize(cls);
void callInitialize(Class cls)
{
((void(*)(Class, SEL))objc_msgSend)(cls, SEL_initialize);
asm("");
}
五、面试题 load 与initialize 的区别
5.1 调用方式:
- load 根据函数地址调用
- initialize 通过objc_msgSend 调用
5.2 调用时刻
- load 属于runtime 加载类、分类的时候,只会调用一次
- initialize 方法上类第一次收到消息时,每个类调用一次,而父类的initialize 可能会调用多次
5.3 调用顺序:
- load:
- 先编译那个类就先调用它的load,父类的load 方法优先。
- 分类中也是先主类执行,后分类执行
- initialize:和普通方法一样,因为执行的是objc_msgSend 。
- 先执行子类的initialize,如果没有,执行父类的。
- 分类部分,如果分类有,执行分类,不执行主类的