本系列博客是本人的源码阅读笔记,如果有 iOS 开发者在看 runtime 的,欢迎大家多多交流。
本文完整版详见笔者小专栏: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,希望对大家有所帮助。