iOS開發之 runtime(30) :remapped_class_map 淺析

本系列博客是本人的源碼閱讀筆記,如果有 iOS 開發者在看 runtime 的,歡迎大家多多交流。爲了方便討論,本人新建了一個微信羣(iOS技術討論羣),想要加入的,請添加本人微信:zhujinhui207407,【加我前請備註:ios 】,本人博客http://www.kyson.cn 也在不停的更新中,歡迎一起討論

本文完整版詳見筆者小專欄:https://xiaozhuanlan.com/runtime

知識點

  • remap class

前言

繼續之前的文章,我們來分析 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 相關的函數:



這裏總結如下:

添加一個 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,希望對大家有所幫助。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章