本页所使用的objc runtime 756.2,来自 Apple 开源文档
类的加载探寻系列:
1、【类的加载】-(1)类的启动
一、加载镜像load_images
前两篇文章学习了objc_init 中的主要方法map_images
,接下来学习加载镜像,先把源码摆出来:
通过注视可以看到,主要执行了2件事务:
- 找到 load 方法
- 调用load 方法。
void
load_images(const char *path __unused, const struct mach_header *mh)
{
// Return without taking locks if there are no +load methods here.
if (!hasLoadMethods((const headerType *)mh)) return;
recursive_mutex_locker_t lock(loadMethodLock);
// Discover load methods
{
mutex_locker_t lock2(runtimeLock);
prepare_load_methods((const headerType *)mh);
}
// Call +load methods (without runtimeLock - re-entrant)
call_load_methods();
}
其中找到load 方法,是通过先找到其背后的类或者分类。
1.1 准备类
下面这行代码表示类
void prepare_load_methods(const headerType *mhdr)
{
size_t count, i;
runtimeLock.assertLocked();
classref_t *classlist =
_getObjc2NonlazyClassList(mhdr, &count);
for (i = 0; i < count; i++) {
schedule_class_load(remapClass(classlist[i]));
}
category_t **categorylist = _getObjc2NonlazyCategoryList(mhdr, &count);
for (i = 0; i < count; i++) {
category_t *cat = categorylist[i];
Class cls = remapClass(cat->cls);
if (!cls) continue; // category for ignored weak-linked class
if (cls->isSwiftStable()) {
_objc_fatal("Swift class extensions and categories on Swift "
"classes are not allowed to have +load methods");
}
realizeClassWithoutSwift(cls);
assert(cls->ISA()->isRealized());
add_category_to_loadable_list(cat);
}
}
找到类的列表:
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. Repeatedly call class +loads until there aren't any more
while (loadable_classes_used > 0) {
call_class_loads();
}
// 2. Call category +loads ONCE
more_categories = call_category_loads();
// 3. Run more +loads if there are classes OR more untried categories
} while (loadable_classes_used > 0 || more_categories);
1.2.1 调用类的load 方法
- 列举可加载的类
- 循环找到类的对应load 方法的method 函数
- 向类发送该method 函数,完成调用
static void call_class_loads(void)
{
int i;
//(1)
// Detach current loadable list.
struct loadable_class *classes = loadable_classes;
int used = loadable_classes_used;
loadable_classes = nil;
loadable_classes_allocated = 0;
loadable_classes_used = 0;
// Call all +loads for the detached list.
for (i = 0; i < used; i++) {
Class cls = classes[i].cls;
//(2)
load_method_t load_method = (load_method_t)classes[i].method;
if (!cls) continue;
if (PrintLoading) {
_objc_inform("LOAD: +[%s load]\n", cls->nameForLogging());
}
//(3)
(*load_method)(cls, SEL_load);
}
// Destroy the detached list.
if (classes) free(classes);
}
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) {
id value = nil;
uintptr_t policy = OBJC_ASSOCIATION_ASSIGN;
{
// 关联对象的管理类
AssociationsManager manager;
AssociationsHashMap &associations(manager.associations());
// 生成伪装地址。处理参数 object 地址
disguised_ptr_t disguised_object = DISGUISE(object);
// 所有对象的额迭代器
AssociationsHashMap::iterator i = associations.find(disguised_object);
if (i != associations.end()) {
ObjectAssociationMap *refs = i->second;
// 内部对象的迭代器
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 (value && (policy & OBJC_ASSOCIATION_GETTER_AUTORELEASE)) {
objc_autorelease(value);
}
return value;
}
3.3 实战
3.3.1 目标
给UIAlertController
添加一个block 回调
3.3.2 添加前
添加钱,每个UIAlertAction 处理自己的逻辑,如果逻辑较多,业务分离开,不方便阅读。
static void *SIGNALERTCONTROLLER = "SIGNALERTCONTROLLER";
- (void)askUserAQuestion {
UIAlertController *ac = [UIAlertController alertControllerWithTitle:@"hai" message:@"hey" preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction *aa = [UIAlertAction actionWithTitle:@"aaa" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
[self doFirst];
}] ;
UIAlertAction *bb = [UIAlertAction actionWithTitle:@"bb" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
[self doSecond];
}] ;
UIAlertAction *cc = [UIAlertAction actionWithTitle:@"cc" style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) {
NSLog(@"cancel");
}] ;
[ac addAction:aa];
[ac addAction:bb];
[ac addAction:cc];
[self presentViewController:ac animated:YES completion:^{}];
}
3.3.3 添加后
统一在block 中处理业务,逻辑更集中
static void *SIGNALERTCONTROLLER = "SIGNALERTCONTROLLER";
- (void)askUserAQuestion {
UIAlertController *ac = [UIAlertController alertControllerWithTitle:@"hai" message:@"hey" preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction *aa = [UIAlertAction actionWithTitle:@"aaa" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
void (^block) (NSInteger) = objc_getAssociatedObject(ac, SIGNALERTCONTROLLER );
block(0);
}] ;
UIAlertAction *bb = [UIAlertAction actionWithTitle:@"bb" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
void (^block) (NSInteger) = objc_getAssociatedObject(ac, SIGNALERTCONTROLLER );
block(1);
}] ;
UIAlertAction *cc = [UIAlertAction actionWithTitle:@"cc" style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) {
void (^block) (NSInteger) = objc_getAssociatedObject(ac, SIGNALERTCONTROLLER );
block(2);
}] ;
[ac addAction:aa];
[ac addAction:bb];
[ac addAction:cc];
void (^block)(NSInteger) = ^(NSInteger index){
NSLog(@"index---> %ld", (long)index);
if (index == 0) {
[self doFirst];
}else if (index == 1){
[self doSecond];
}else{
NSLog(@"cancel");
}
};
objc_setAssociatedObject(ac,
SIGNALERTCONTROLLER,
block,
OBJC_ASSOCIATION_COPY);
[self presentViewController:ac animated:YES completion:^{}];
}
- (void)doFirst{}
- (void)doSecond{}
四、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,如果没有,执行父类的。
- 分类部分,如果分类有,执行分类,不执行主类的