iOS开发之 runtime(30) :remapped_cla

作者: kyson老师 | 来源:发表于2019-06-23 16:40 被阅读12次
    logo

    本系列博客是本人的源码阅读笔记,如果有 iOS 开发者在看 runtime 的,欢迎大家多多交流。为了方便讨论,本人新建了一个微信群(iOS技术讨论群),想要加入的,请添加本人微信:zhujinhui207407,【加我前请备注:ios 】,本人博客http://www.kyson.cn 也在不停的更新中,欢迎一起讨论

    本文完整版详见笔者小专栏:https://xiaozhuanlan.com/runtime

    知识点

    • remap class

    前言

    继续之前的文章,我们来分析 remapped_class_map
    全局搜索 remapped_class_map 我们看到如下结果:


    搜索 remapped_class_map

    可以发现,关于 remapped_class_map 的一些引用都位于文件 objc-runtime-new.m 中。

    其中,获取 remapped_class_map 的方法为:

    static NXMapTable *remappedClasses(bool create)
    {
        static NXMapTable *remapped_class_map = nil;
        runtimeLock.assertLocked();
        if (remapped_class_map) return remapped_class_map;
        if (!create) return nil;
        // remapped_class_map is big enough to hold CF's classes and a few others
        INIT_ONCE_PTR(remapped_class_map, 
                      NXCreateMapTable(NXPtrValueMapPrototype, 32), 
                      NXFreeMapTable(v));
        return remapped_class_map;
    

    在该方法上下查看,可以发现一系列和 remap 相关的函数:


    remap 相关函数

    这里总结如下:

    添加一个 class:

    static void addRemappedClass(Class oldcls, Class newcls) {
        runtimeLock.assertWriting();
        if (PrintFuture) {
            _objc_inform("FUTURE: using %p instead of %p for %s", 
                         (void*)newcls, (void*)oldcls, oldcls->nameForLogging());
        }
        void *old;
        old = NXMapInsert(remappedClasses(YES), oldcls, newcls);
        assert(!old);
    }
    

    将一个 class 进行 remap:

    static Class remapClass(classref_t cls)
    {
        return remapClass((Class)cls);
    }
    

    大家注意一下,这边的参数类型是 classref_t,经过前面的文章,我们了解到,这是从 secion 中取出来的原始的 class 类型。
    这个方法是在 _read_images 方法中被调用的:

    // Realize non-lazy classes (for +load methods and static instances)
    for (EACH_HEADER) {
        classref_t *classlist =
        _getObjc2NonlazyClassList(hi, &count);
        for (i = 0; i < count; i++) {
            Class cls = remapClass(classlist[i]);
            if (!cls) continue;
            realizeClass(cls);
        }
    }
    

    remapped_class_map 相关的方法介绍完了,但大家可能还是一头雾水

    • 什么是 remapped class ?
    • 为什么 class 要 remap?
    • remap 过程?

    本文就带大家研究一下 remap class 的实现。

    分析

    什么是 remapped class ?

    remap class,字面意思是 重新映射 class,那肯定有一个映射者和映射结果。所以我们看方法:

    static Class remapClass(classref_t cls) {
        return remapClass((Class)cls);
    }
    

    参数类型是 classref_t,返回值类型是 Class。classref_t 我们很熟悉了,在前面的文章中我们知道,section 为 __objc_classlist 以及 __objc_nlclslist 的返回类型都是 classref_t,也就是说, remap 的参数从这两个 section 中拿到的。其实现如下:

    static Class remapClass(Class cls)
    {
        runtimeLock.assertLocked();
        Class c2;
        if (!cls) return nil;
        NXMapTable *map = remappedClasses(NO);
        if (!map  ||  NXMapMember(map, cls, (void**)&c2) == NX_MAPNOTAKEY) {
            return cls;
        } else {
            return c2;
        }
    }
    

    从该方法实现中可以看出:map 的键是 cls,也就是 section 中拿到的 cls,而 value 就是我们 remap 的结果。而 remap 的 操作肯定是在方法

    static void addRemappedClass(Class oldcls, Class newcls) {
    }
    

    中的,因为两个参数分别为 oldcls ,newcls。

    为什么 Class 要 remap?

    要知道原因,我们看一下调用时机,他们都在方法 readClass 中,其调用栈为:
    全局搜索 addRemappedClass,发现其实调用时机只有一个地方,且调用栈为:

    void _objc_init()
        void map_2_images()
            void map_images_nolock()
                void _read_images()
                    Class readClass()
                        void addRemappedClass()
    

    其实现为:

    Class readClass(Class cls, bool headerIsBundle, bool headerIsPreoptimized)
    {
        const char *mangledName = cls->mangledName();
        if (missingWeakSuperclass(cls)) {
            addRemappedClass(cls, nil);
            cls->superclass = nil;
            return nil;
        }
    
        Class replacing = nil;
        if (Class newCls = popFutureNamedClass(mangledName)) {
            if (newCls->isSwift()) {
                _objc_fatal("Can't complete future class request for '%s' "
                            "because the real class is too big.", 
                            cls->nameForLogging());
            }
            
            class_rw_t *rw = newCls->data();
            const class_ro_t *old_ro = rw->ro;
            memcpy(newCls, cls, sizeof(objc_class));
            rw->ro = (class_ro_t *)newCls->data();
            newCls->setData(rw);
            freeIfMutable((char *)old_ro->name);
            free((void *)old_ro);
            
            addRemappedClass(cls, newCls);
            
            replacing = cls;
            cls = newCls;
        }
        
        if (headerIsPreoptimized  &&  !replacing) {
            assert(getClass(mangledName));
        } else {
            addNamedClass(cls, mangledName, replacing);
        }
        
        // for future reference: shared cache never contains MH_BUNDLEs
        if (headerIsBundle) {
            cls->data()->flags |= RO_FROM_BUNDLE;
            cls->ISA()->data()->flags |= RO_FROM_BUNDLE;
        }
        
        return cls;
    }
    

    如上代码可知,有两个分支有机会进入方法 addRemappedClass,一个是 missingWeakSuperclass 方法是否为真,另外一个是 popFutureNamedClass(mangledName) 方法返回是否为真。这里会有两个概念

    1. WeakSuperclass
    2. FutureNamedClass

    而这两个条件为true 的情况就是需要 remap 的情况。至于这两个方法的作用是什么,笔者后面再一一分解,今天就先分析到这里了。

    总结

    本文笔者带大家分析了另外一个和 class 相关的 hashmap:remapped_class_map,希望对大家有所帮助。

    相关文章

      网友评论

        本文标题:iOS开发之 runtime(30) :remapped_cla

        本文链接:https://www.haomeiwen.com/subject/ahxwqctx.html