【类的加载】-(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;
    
        }
  1. 向分类发送该method 函数,完成调用

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

    这里通过一个叫做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);
  4. 根据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);
                    }
  5. 没有对象的关联信息情况。创建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());
  1. 生成伪装地址。处理参数 object 地址

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

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

    if (i != associations.end()) {
                ObjectAssociationMap *refs = i->second;
                // 内部对象的迭代器
                ObjectAssociationMap::iterator j = refs->find(key);
    
    }
  1. 查找——找到 - 把值和策略读取出来

    // 内部对象的迭代器
                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 李佳
  目录