iOS开发之 runtime(30) :remapped_class_map 浅析

logo

本系列博客是本人的源码阅读笔记,如果有 iOS 开发者在看 runtime 的,欢迎大家多多交流。

本文完整版详见笔者小专栏: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_class_map 浅析)