【类的加载】-(3)loading_images


本页所使用的objc runtime 756.2,来自 Apple 开源文档

类的加载探寻系列:
1、【类的加载】-(1)类的启动

2、【类的加载】-(2)懒加载类与分类

3、【类的加载】-(3)loading_images

一、加载镜像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 方法

  1. 列举可加载的类
  2. 循环找到类的对应load 方法的method 函数
  3. 向类发送该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 方法

  1. 列举可加载的分类

    // 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;
  2. 循环找到分类的对应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;

    }
  3. 向分类发送该method 函数,完成调用

    (*load_method)(cls, SEL_load);
  4. 释放可执行分类列表,保证程序启动只调用一次该加载。

    这里通过一个叫做loadable_categories_usedused 的标识,来决定是否加载,并进行摧毁。

    把使用过的

    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 关联对象的使用

  1. 添加关联。

    主要用到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);

    }
  2. 获取关联属性

    主要函数为: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 进入迭代器对每个类表查找
  • 如果找到,对新添加的属性进行绑定
  • 如果找不到,创建一个,进行绑定存储
  1. 通过manager 管理

    // 关联对象的管理类
    AssociationsManager manager;
  2. 获取关联的 HashMap -> 存储当前关联对象

    AssociationsHashMap &associations(manager.associations());
  3. 获取 AssociationsHashMap 的迭代器 - (对象的) 进行遍历

AssociationsHashMap::iterator i = associations.find(disguised_object);
  1. 根据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);
    }
  2. 没有对象的关联信息情况。创建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 关联对象查找原理

主要有以下几个步骤:

  1. 生成关联对象的管理类

    AssociationsManager manager;
    AssociationsHashMap &associations(manager.associations());
  2. 生成伪装地址。处理参数 object 地址

    disguised_ptr_t disguised_object = DISGUISE(object);
  3. 生成Hash表中所有对象的额迭代器

    AssociationsHashMap::iterator i = associations.find(disguised_object);
  4. 对象内部继续生称迭代器,准备迭代属性

    if (i != associations.end()) {
    ObjectAssociationMap *refs = i->second;
    // 内部对象的迭代器
    ObjectAssociationMap::iterator j = refs->find(key);

    }
  5. 查找——找到 - 把值和策略读取出来

    // 内部对象的迭代器
    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);
    }
    }
    1. 如果找到,就进行持有

      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,如果没有,执行父类的。
    • 分类部分,如果分类有,执行分类,不执行主类的

文章作者: 李佳
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 李佳 !
评论
 上一篇
【数据结构与算法】-(2)线性表基础 【数据结构与算法】-(2)线性表基础
【数据结构与算法】-(1)基础篇 【数据结构与算法】-(2)线性表基础 【数据结构与算法】-(3)循环链表(单向) 【数据结构与算法】-(4)双向链表和双向循环链表 【数据结构与算法】-(5)链表面试题解析 【数据结构与算法】
2020-04-01 李佳
下一篇 
【数据结构与算法】-(1)基础篇 【数据结构与算法】-(1)基础篇
算法是解决特定问题对求解步骤的描述,在计算机中表现为指令的有限序列,并且每条指令表示一个或多个操作。
2020-03-31 李佳
  目录