本系列博客是本人的源碼閱讀筆記,如果有 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) 方法返回是否爲真。這裏會有兩個概念
- WeakSuperclass
- FutureNamedClass
而這兩個條件爲true 的情況就是需要 remap 的情況。至於這兩個方法的作用是什麼,筆者後面再一一分解,今天就先分析到這裏了。
總結
本文筆者帶大家分析了另外一個和 class 相關的 hashmap:remapped_class_map,希望對大家有所幫助。