在上一篇文章 iOS 底层 dyld 与 objc 的关联 中分析了 dyld
与 objc
是如何关联上的,下面来了解下类的相关信息是如何加载到内存上的。
在 _dyld_objc_notify_register
注册回调中有带三个参数,我们重点看下 map_images
和 load_images
map_images:管理文件中和动态库中的所有符号,即class、protocol、selector、category等
load_images:加载执行load方法
其中代码通过编译转换成可执行文件(Mach-o),然后将 Mach-o 读取到内存,如下所示
map_images 加载镜像文件到内存
map_images 主要是将 mach-o
文件加载到内存中
-
- 进入
map_images
的源码,如下
- 进入
/***********************************************************************
* map_images
* Process the given images which are being mapped in by dyld.
* Calls ABI-agnostic code after taking ABI-specific locks.
*
* Locking: write-locks runtimeLock
**********************************************************************/
void
map_images(unsigned count, const char * const paths[],
const struct mach_header * const mhdrs[])
{
mutex_locker_t lock(runtimeLock);
return map_images_nolock(count, paths, mhdrs);
}
-
- 进入
map_images_nolock
的源码,查看其具体的实现
- 进入
源码很长,主要是查找 mach-o
中的 Objective-C
数据,从源码实现中可以看出,_read_images
是关键代码,下面我们着重看下它的实现
_read_images 源码
源码很长,主要功能分为以下部分:
-
- 条件控制进行的一次加载
-
- 修复预编译阶段的@selector的混乱问题
-
- 错误混乱的类处理
-
- 修复重映射一些没有被镜像文件加载进来的类
-
- 修复一些消息
-
- 当类里面有协议时,readProtocol
-
- 修复没有被加载的协议
-
- 分类处理
-
- 类的加载处理
-
- 没有被处理的类,优化那些被侵犯的类
1. 条件控制进行的一次加载
主要是通过 NXCreateMapTable
创建表,存放了已经命名的类的信息,目的是为了方便查找、快捷
if (!doneOnce) {
doneOnce = YES;
launchTime = YES;
#if SUPPORT_NONPOINTER_ISA
// Disable non-pointer isa under some conditions.
# if SUPPORT_INDEXED_ISA
// Disable nonpointer isa if any image contains old Swift code
for (EACH_HEADER) {...}
# endif
# if TARGET_OS_OSX
// Disable non-pointer isa if the app is too old
// (linked before OS X 10.11)
if (dyld_get_program_sdk_version() < DYLD_MACOSX_VERSION_10_11) {...}
// Disable non-pointer isa if the app has a __DATA,__objc_rawisa section
// New apps that load old extensions may need this.
for (EACH_HEADER) {...}
# endif
#endif
if (DisableTaggedPointers) {
disableTaggedPointers();
}
initializeTaggedPointerObfuscator();
if (PrintConnecting) {
_objc_inform("CLASS: found %d classes during launch", totalClasses);
}
// namedClasses
// Preoptimized classes don't go in this table.
// 4/3 is NXMapTable's load factor
int namedClassesSize =
(isPreoptimized() ? unoptimizedTotalClasses : totalClasses) * 4 / 3;
// 创建哈希表
gdb_objc_realized_classes =
NXCreateMapTable(NXStrValueMapPrototype, namedClassesSize);
ts.log("IMAGE TIMES: first time tasks");
}
前面代码做了一些容错处理。关于哈希表 gdb_objc_realized_classes
官方文档有说明
// This is a misnomer: gdb_objc_realized_classes is actually a list of
// named classes not in the dyld shared cache, whether realized or not.
// gdb_objc_realized_classes 实际上是一张已经命名的类的表,无论它是否实现都不在 dyld 共享缓存中
NXMapTable *gdb_objc_realized_classes; // exported for debuggers in objc-gdb.h
2. 修复预编译阶段的@selector的混乱问题
主要是通过通过 _getObjc2SelectorRefs
拿到 Mach_O
中的静态段__objc_selrefs
,保证 sel_registerNameNoLock
获取的 sel
与 _getObjc2SelectorRefs
中的 sel
一致
// Fix up @selector references
// sel 不是简单的字符串,而是带地址的字符串
static size_t UnfixedSelectors;
{
mutex_locker_t lock(selLock);
for (EACH_HEADER) {
if (hi->hasPreoptimizedSelectors()) continue;
bool isBundle = hi->isBundle();
// 通过 _getObjc2SelectorRefs 拿到 Mach-O 中的静态段 __objc_selrefs
SEL *sels = _getObjc2SelectorRefs(hi, &count);
UnfixedSelectors += count;
for (i = 0; i < count; i++) {
const char *name = sel_cname(sels[i]);
// 注册 sel 操作,即将 sel 添加到 namedSelectors 哈希表中
SEL sel = sel_registerNameNoLock(name, isBundle);
// 当 sel 与 sels[i] 地址不一致时,需要调整为一致的
if (sels[i] != sel) {
sels[i] = sel;
}
}
}
}
其中 sel_registerNameNoLock
的源码实现如下
SEL sel_registerNameNoLock(const char *name, bool copy) {
return __sel_registerName(name, 0, copy); // NO lock, maybe copy
}
static SEL __sel_registerName(const char *name, bool shouldLock, bool copy)
{
SEL result = 0;
if (shouldLock) selLock.assertUnlocked();
else selLock.assertLocked();
if (!name) return (SEL)0;
result = search_builtins(name);
if (result) return result;
conditional_mutex_locker_t lock(selLock, shouldLock);
// sel 插入
auto it = namedSelectors.get().insert(name);
if (it.second) {
// No match. Insert.
*it.first = (const char *)sel_alloc(name, copy);
}
return (SEL)*it.first;
}
关键代码 auto it = namedSelectors.get().insert(name);
将 sel
插入 namedSelectors
哈希表。其中 sel
并不是简单的字符串,是带地址的字符串。我们可以通过断点调试验证,如下
3. 错误混乱的类处理
主要是从 Mach-O
中取出所有类,再遍历进行处理
// Discover classes. Fix up unresolved future classes. Mark bundle classes.
bool hasDyldRoots = dyld_shared_cache_some_image_overridden();
for (EACH_HEADER) {
if (! mustReadClasses(hi, hasDyldRoots)) {
// Image is sufficiently optimized that we need not call readClass()
continue;
}
// 从 Mach-O 中获取静态段 __objc_classlist,是一个 classref_t 类型的指针(编译后的类列表中)
classref_t const *classlist = _getObjc2ClassList(hi, &count);
bool headerIsBundle = hi->isBundle();
bool headerIsPreoptimized = hi->hasPreoptimizedClasses();
for (i = 0; i < count; i++) {
Class cls = (Class)classlist[I];
Class newCls = readClass(cls, headerIsBundle, headerIsPreoptimized);
// 经过调试,下面的流程并未执行
if (newCls != cls && newCls) {
// Class was moved but not deleted. Currently this occurs
// only when the new class resolved a future class.
// Non-lazily realize the class below.
resolvedFutureClasses = (Class *)
realloc(resolvedFutureClasses,
(resolvedFutureClassCount+1) * sizeof(Class));
resolvedFutureClasses[resolvedFutureClassCount++] = newCls;
}
}
}
其核心代码是 readClass
,在未执行 readClass
前后, cls
有什么变化呢,我们打个断点,调试如下
从上面调试中可以看到,在 readClass
前,cls
还只是一个地址,readClass
之后,cls
获取就变成了名字。到此,类的信息仅仅存储了类的地址和名字
4. 修复重映射一些没有被镜像文件加载进来的类
主要是将未映射的 Class
和 Super Class
进行重映射
// Fix up remapped classes
// Class list and nonlazy class list remain unremapped.
// Class refs and super refs are remapped for message dispatching.
if (!noClassesRemapped()) {
for (EACH_HEADER) {
// 获取 Mach-O 中的静态段 __objc_classrefs(类的引用)
Class *classrefs = _getObjc2ClassRefs(hi, &count);
for (i = 0; i < count; i++) {
remapClassRef(&classrefs[I]);
}
// fixme why doesn't test future1 catch the absence of this?
// 获取 Mach-O 中的静态段 __objc_superrefs(父类的引用)
classrefs = _getObjc2SuperRefs(hi, &count);
for (i = 0; i < count; i++) {
remapClassRef(&classrefs[I]);
}
}
}
从源码注释中,可以看到需要被 remapClassRef
的类是懒加载类(没有手动实现 +load),所以调试的时候这部分代码是没有走的
5. 修复一些消息
主要是遍历 refs
,通过 fixupMessageRef
将函数指针进行注册,并 fix
为新的函数指针
// Fix up old objc_msgSend_fixup call sites
for (EACH_HEADER) {
// 通过 _getObjc2MessageRefs 获取 Mach-O 的静态段 __objc_msgrefs
message_ref_t *refs = _getObjc2MessageRefs(hi, &count);
if (count == 0) continue;
if (PrintVtables) {
_objc_inform("VTABLES: repairing %zu unsupported vtable dispatch "
"call sites in %s", count, hi->fname());
}
// 遍历
for (i = 0; i < count; i++) {
fixupMessageRef(refs+i);
}
}
6. 当类里面有协议时:readProtocol
主要是将从静态段获取的协议列表,循环遍历添加到协议哈希表中
bool cacheSupportsProtocolRoots = sharedCacheSupportsProtocolRoots();
// Discover protocols. Fix up protocol refs.
for (EACH_HEADER) {
extern objc_class OBJC_CLASS_$_Protocol;
Class cls = (Class)&OBJC_CLASS_$_Protocol;
ASSERT(cls);
// 获取哈希表
NXMapTable *protocol_map = protocols();
bool isPreoptimized = hi->hasPreoptimizedProtocols();
// Skip reading protocols if this is an image from the shared cache
// and we support roots
// Note, after launch we do need to walk the protocol as the protocol
// in the shared cache is marked with isCanonical() and that may not
// be true if some non-shared cache binary was chosen as the canonical
// definition
if (launchTime && isPreoptimized && cacheSupportsProtocolRoots) {
if (PrintProtocols) {
_objc_inform("PROTOCOLS: Skipping reading protocols in image: %s",
hi->fname());
}
continue;
}
bool isBundle = hi->isBundle();
// 通过 _getObjc2ProtocolList 获取到 Mach-O 中的静态段 __objc_protolist 协议列表
protocol_t * const *protolist = _getObjc2ProtocolList(hi, &count);
// 遍历协议列表
for (i = 0; i < count; i++) {
// 将协议添加到 protocol_map 哈希表中
readProtocol(protolist[i], cls, protocol_map,
isPreoptimized, isBundle);
}
}
7. 修复没有被加载的协议
通过获取静态段 _getObjc2ProtocolRefs
(与上面的 _getObjc2ProtocolList
不一样),遍历需要修复的协议,通过 remapProtocolRef
比较当前协议和协议列表中的同一个内存地址的协议是否相同,如果不同则替换
// Fix up @protocol references
// Preoptimized images may have the right
// answer already but we don't know for sure.
for (EACH_HEADER) {
// At launch time, we know preoptimized image refs are pointing at the
// shared cache definition of a protocol. We can skip the check on
// launch, but have to visit @protocol refs for shared cache images
// loaded later.
if (launchTime && cacheSupportsProtocolRoots && hi->isPreoptimized())
continue;
// 获取到 Mach-O 的静态段 __objc_protorefs
protocol_t **protolist = _getObjc2ProtocolRefs(hi, &count);
// 遍历
for (i = 0; i < count; i++) {
// 比较当前协议和协议列表中的同一个内存地址的协议是否相同,如果不同则替换
remapProtocolRef(&protolist[I]);
}
}
remapProtocolRef
的源码实现如下
/***********************************************************************
* remapProtocolRef
* Fix up a protocol ref, in case the protocol referenced has been reallocated.
* Locking: runtimeLock must be read- or write-locked by the caller
**********************************************************************/
static size_t UnfixedProtocolReferences;
static void remapProtocolRef(protocol_t **protoref)
{
runtimeLock.assertLocked();
// 取协议列表中的实时协议指针
protocol_t *newproto = remapProtocol((protocol_ref_t)*protoref);
if (*protoref != newproto) {
// 如果当前协议与同一内存地址协议不同,则替换
*protoref = newproto;
UnfixedProtocolReferences++;
}
}
8. 分类处理
处理分类,仅在完成初始类别并将数据加载到类后才执行,对于启动时出现的分类,将分类的发现推迟推迟到对 _dyld_objc_notify_register
的调用完成后的第一个 load_images
调用完成
// Discover categories. Only do this after the initial category
// attachment has been done. For categories present at startup,
// discovery is deferred until the first load_images call after
// the call to _dyld_objc_notify_register completes. rdar://problem/53119145
if (didInitialAttachCategories) {
for (EACH_HEADER) {
load_categories_nolock(hi);
}
}
9. 类的加载处理
主要是实现类的加载,实现非懒加载类。源码如下
// Realize non-lazy classes (for +load methods and static instances)
// 实现非懒加载类(实现了 +load 方法或者 静态实例)
for (EACH_HEADER) {
// 获取 Mach-O 的静态段 __objc_nlclslist (非懒加载类表)
classref_t const *classlist =
_getObjc2NonlazyClassList(hi, &count);
// 遍历
for (i = 0; i < count; i++) {
// 重映射
Class cls = remapClass(classlist[i]);
if (!cls) continue;
// 插入表,但是前面已经插入过了,所以不会重新插入
addClassTableEntry(cls);
if (cls->isSwiftStable()) {
if (cls->swiftMetadataInitializer()) {
_objc_fatal("Swift class %s with a metadata initializer "
"is not allowed to be non-lazy",
cls->nameForLogging());
}
// fixme also disallow relocatable classes
// We can't disallow all Swift classes because of
// classes like Swift.__EmptyArrayStorage
}
// 实现当前的类,因为前面 readClass 读取到内存的仅仅只有地址+名称,类的 data 数据并没有加载出来
// 实现所有非懒加载的类(实例化类对象的一些信息,例如rw)
realizeClassWithoutSwift(cls, nil);
}
}
-
_getObjc2NonlazyClassList
获取Mach-O
的静态段__objc_nlclslist
(非懒加载类表) -
addClassTableEntry
将非懒加载类插入类表,存储到内存,如果已经添加就不会载添加,需要确保整个结构都被添加 -
realizeClassWithoutSwift
实现当前的类,在前面readClass
读取到内存的仅仅只有地址+名称,类的data
数据还没有加载
10. 没有被处理的类,优化那些被侵犯的类
主要是实现没有被处理的类,优化被侵犯的类
// Realize newly-resolved future classes, in case CF manipulates them
if (resolvedFutureClasses) {
for (i = 0; i < resolvedFutureClassCount; i++) {
Class cls = resolvedFutureClasses[I];
if (cls->isSwiftStable()) {
_objc_fatal("Swift class is not allowed to be future");
}
// 实现类
realizeClassWithoutSwift(cls, nil);
cls->setInstancesRequireRawIsaRecursively(false/*inherited*/);
}
free(resolvedFutureClasses);
}
ts.log("IMAGE TIMES: realize future classes");
if (DebugNonFragileIvars) {
// 实现所有类
realizeAllClasses();
}
在以上的分析中,我们需要重点关注 3 中的
readClass
(读取类)和 9 中的realizeClassWithoutSwift
(实现类)。下面来展开分析这两个方法
readClass 读取类
前面介绍过,在调用 readClass
前,cls
还只是一个地址,执行该方法后 cls
就变成了类的名字。这个方法中做了怎样的处理呢?我们跳到源码,如下
/***********************************************************************
* readClass
* Read a class and metaclass as written by a compiler.
* Returns the new class pointer. This could be:
* - cls
* - nil (cls has a missing weak-linked superclass)
* - something else (space for this class was reserved by a future class)
*
* Note that all work performed by this function is preflighted by
* mustReadClasses(). Do not change this function without updating that one.
*
* Locking: runtimeLock acquired by map_images or objc_readClassPair
**********************************************************************/
Class readClass(Class cls, bool headerIsBundle, bool headerIsPreoptimized)
{
const char *mangledName = cls->mangledName();
// 当前类的父类中若有丢失的weak-linked类,则返回nil
if (missingWeakSuperclass(cls)) {
// No superclass (probably weak-linked).
// Disavow any knowledge of this subclass.
if (PrintConnecting) {
_objc_inform("CLASS: IGNORING class '%s' with "
"missing weak-linked superclass",
cls->nameForLogging());
}
addRemappedClass(cls, nil);
cls->superclass = nil;
return nil;
}
cls->fixupBackwardDeployingStableSwift();
// 判断是不是后期要处理的类,正常情况下,不会走到popFutureNamedClass,因为这是专门针对未来待处理的类的操作。因此不会对ro、rw进行操作
Class replacing = nil;
if (Class newCls = popFutureNamedClass(mangledName)) {
// This name was previously allocated as a future class.
// Copy objc_class to future class's struct.
// Preserve future's rw data block.
if (newCls->isAnySwift()) {
_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->set_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) {
// class list built in shared cache
// fixme strict assert doesn't work because of duplicates
// ASSERT(cls == getClass(name));
ASSERT(getClassExceptSomeSwift(mangledName));
} else {
// 加载共享缓存中的类
addNamedClass(cls, mangledName, replacing);
// 插入表,即相当于从 mach-O 文件读取到内存中
addClassTableEntry(cls);
}
// 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;
}
其中关键代码是 addNamedClass
和 addClassTableEntry
。
-
- 通过
mangledName
获取类名,源码实现如下
- 通过
const char *mangledName() {
// fixme can't assert locks here
ASSERT(this);
if (isRealized() || isFuture()) {
// 如果实现的类或则未实现的未来的类,则从 ro 中获取 name
return data()->ro()->name;
} else {
// 从 mach-O 的数据 data 中获取 name
return ((const class_ro_t *)data())->name;
}
}
-
- 当前类的父类中若有丢失的
weak-linked
类,则返回 nil
- 当前类的父类中若有丢失的
-
- 判断是不是后期需要处理的类,正常情况下,不会走到
popFutureNamedClass
,这是专门针对未来待处理的类的操作。可以通过断点调试验证
-
data()
是Mach-o
的数据,并不在 class 内存中 -
rw
的赋值是从mach-o
中的data
强转赋值的 -
rw
里的ro
是从ro
复制过去的
- 判断是不是后期需要处理的类,正常情况下,不会走到
-
- 通过
addNamedClass
将当前类添加到已经创建好的gdb_objc_realized_classes
哈希表,该表用于存放所有类
- 通过
/***********************************************************************
* addNamedClass
* Adds name => cls to the named non-meta class map.
* Warns about duplicate class names and keeps the old mapping.
* Locking: runtimeLock must be held by the caller
**********************************************************************/
static void addNamedClass(Class cls, const char *name, Class replacing = nil)
{
runtimeLock.assertLocked();
Class old;
if ((old = getClassExceptSomeSwift(name)) && old != replacing) {
inform_duplicate(name, old, cls);
// getMaybeUnrealizedNonMetaClass uses name lookups.
// Classes not found by name lookup must be in the
// secondary meta->nonmeta table.
addNonMetaClass(cls);
} else {
NXMapInsert(gdb_objc_realized_classes, name, cls);
}
ASSERT(!(cls->data()->flags & RO_META));
// wrong: constructed classes are already realized when they get here
// ASSERT(!cls->isRealized());
}
-
- 通过
addClassTableEntry
,将初始化的类添加到allocatedClasses
表
- 通过
/***********************************************************************
* addClassTableEntry
* Add a class to the table of all classes. If addMeta is true,
* automatically adds the metaclass of the class as well.
* Locking: runtimeLock must be held by the caller.
**********************************************************************/
static void
addClassTableEntry(Class cls, bool addMeta = true)
{
runtimeLock.assertLocked();
// This class is allowed to be a known class via the shared cache or via
// data segments, but it is not allowed to be in the dynamic table already.
auto &set = objc::allocatedClasses.get();
ASSERT(set.find(cls) == set.end());
if (!isKnownClass(cls)) // 没有这个类
set.insert(cls);
if (addMeta) // 元类
addClassTableEntry(cls->ISA(), false);
}
如果我们想在源码中定位自定义的类,可以在获取类名的下面添加自定义判断,如下
const char *customClsName = "LGPerson";
if (strcmp(mangledName, customClsName) == 0) {
printf("%s --- 这个是我要研究的 --- %s", __func__, mangledName);
}
readClass
的主要作用就是将Mach-o
中的类读取到内存,即插入表中,目前的类仅有两个信息:地址以及名称,其他数据还未展示出来(如data
数据)
realizeClassWithoutSwift 实现类
realizeClassWithoutSwift
主要作用实现类,将类的 data
数据加载到内存。源码很长,这里主要分析关键代码部分,其主要操作如下
-
- 从
mach-o
中读取data
数据,并设置ro
、rw
- 从
-
- 递归调用
realizeClassWithoutSwift
,确定继承链
- 递归调用
-
- 通过
methodizeClass
方法化类
- 通过
1. data 数据的读取
从 mach-o
中读取 data
数据,将其强转为 ro
,以及 rw
初始化,设置 rw
中的 ro
,源码如下
// fixme verify class is not in an un-dlopened part of the shared cache?
auto ro = (const class_ro_t *)cls->data(); // 读取类机构的 bits 属性
auto isMeta = ro->flags & RO_META;
if (ro->flags & RO_FUTURE) { // 如果是元类
// This was a future class. rw data is already allocated.
rw = cls->data(); // rw 赋值
ro = cls->data()->ro(); // ro 赋值
ASSERT(!isMeta);
cls->changeInfo(RW_REALIZED|RW_REALIZING, RW_FUTURE);
} else {
// Normal class. Allocate writeable class data.
rw = objc::zalloc(); //申请开辟 rw 空间
rw->set_ro(ro); // 设置 rw 中的 ro
rw->flags = RW_REALIZED|RW_REALIZING|isMeta;
cls->setData(rw); // 将 cls 的 data 赋值为 rw
}
ro: (readOnly 只读),在编译时已经确定了内存,包含类名、方法、协议、实例变量等信息。它是一片干净的内存(cleanMemory),即加载后不会再发生改变
rw: (readWrite 可读可写),由于 iOS 运行时,可能会不断的内存增删改查,防止对原始数据更改,将原始数据(ro)拷贝一份到
rw
;但是并不是每个类都会进行动态的插入(可能有几万个类或几十万个类,被修改的就那么多,极少数),所以就有了rwe
rwe(dirty memory 胀内存),只要动态处理了,才会生成对应的
rwe
。一般情况下,rw
是从ro
里面读取的,如果有运行时,则从rwe
中读取
const class_ro_t *ro() const {
auto v = get_ro_or_rwe();
if (slowpath(v.is())) {
return v.get()->ro;
}
return v.get();
}
2. 递归循环,确定继承链
主要是通过递归循环确定继承链(父类、元类)
// Realize superclass and metaclass, if they aren't already.
// This needs to be done after RW_REALIZED is set above, for root classes.
// This needs to be done after class index is chosen, for root metaclasses.
// This assumes that none of those classes have Swift contents,
// or that Swift's initializers have already been called.
// fixme that assumption will be wrong if we add support
// for ObjC subclasses of Swift classes.
// 确定继承链
supercls = realizeClassWithoutSwift(remapClass(cls->superclass), nil);
metacls = realizeClassWithoutSwift(remapClass(cls->ISA()), nil);
#if SUPPORT_NONPOINTER_ISA
if (isMeta) {
// Metaclasses do not need any features from non pointer ISA
// This allows for a faspath for classes in objc_retain/objc_release.
cls->setInstancesRequireRawIsa();
} else {
// Disable non-pointer isa for some classes and/or platforms.
// Set instancesRequireRawIsa.
bool instancesRequireRawIsa = cls->instancesRequireRawIsa();
bool rawIsaIsInherited = false;
static bool hackedDispatch = false;
if (DisableNonpointerIsa) {
// Non-pointer isa disabled by environment or app SDK version
instancesRequireRawIsa = true;
}
else if (!hackedDispatch && 0 == strcmp(ro->name, "OS_object"))
{
// hack for libdispatch et al - isa also acts as vtable pointer
hackedDispatch = true;
instancesRequireRawIsa = true;
}
else if (supercls && supercls->superclass &&
supercls->instancesRequireRawIsa())
{
// This is also propagated by addSubclass()
// but nonpointer isa setup needs it earlier.
// Special case: instancesRequireRawIsa does not propagate
// from root class to root metaclass
instancesRequireRawIsa = true;
rawIsaIsInherited = true;
}
if (instancesRequireRawIsa) {
cls->setInstancesRequireRawIsaRecursively(rawIsaIsInherited);
}
}
// SUPPORT_NONPOINTER_ISA
#endif
// Update superclass and metaclass in case of remapping
// 更新 cls 的父类以及元类的映射
cls->superclass = supercls;
cls->initClassIsa(metacls);
// Reconcile instance variable offsets / layout.
// This may reallocate class_ro_t, updating our ro variable.
if (supercls && !isMeta) reconcileInstanceVariables(cls, supercls, ro);
// Set fastInstanceSize if it wasn't set already.
cls->setInstanceSize(ro->instanceSize);
// Copy some flags from ro to rw
if (ro->flags & RO_HAS_CXX_STRUCTORS) {
cls->setHasCxxDtor();
if (! (ro->flags & RO_HAS_CXX_DTOR_ONLY)) {
cls->setHasCxxCtor();
}
}
// Propagate the associated objects forbidden flag from ro or from
// the superclass.
if ((ro->flags & RO_FORBIDS_ASSOCIATED_OBJECTS) ||
(supercls && supercls->forbidsAssociatedObjects()))
{
rw->flags |= RW_FORBIDS_ASSOCIATED_OBJECTS;
}
// Connect this class to its superclass's subclass lists
// 父类继承链衔接
if (supercls) {
addSubclass(supercls, cls);
} else {
addRootClass(cls);
}
3. methodizeClass 方法化类
从 ro
中读取方法列表(包括分类中的方法)、属性列表、协议列表赋值给 rw
,并返回 cls
,源码如下
/***********************************************************************
* methodizeClass
* Fixes up cls's method list, protocol list, and property list.
* Attaches any outstanding categories.
* Locking: runtimeLock must be held by the caller
**********************************************************************/
static void methodizeClass(Class cls, Class previously)
{
runtimeLock.assertLocked();
bool isMeta = cls->isMetaClass();
auto rw = cls->data();
auto ro = rw->ro();
auto rwe = rw->ext();
const char *mangledName = cls->mangledName();
// Methodizing for the first time
if (PrintConnecting) {
_objc_inform("CLASS: methodizing class '%s' %s",
cls->nameForLogging(), isMeta ? "(meta)" : "");
}
// Install methods and properties that the class implements itself.
// 获取 ro 中的方法列表
method_list_t *list = ro->baseMethods();
if (list) {
// 排序
prepareMethodLists(cls, &list, 1, YES, isBundleClass(cls));
// 如果是运行时,将方法列表拷贝到 rwe 中
if (rwe) rwe->methods.attachLists(&list, 1);
}
// 属性列表处理
property_list_t *proplist = ro->baseProperties;
if (rwe && proplist) {
rwe->properties.attachLists(&proplist, 1);
}
// 协议列表处理
protocol_list_t *protolist = ro->baseProtocols;
if (rwe && protolist) {
rwe->protocols.attachLists(&protolist, 1);
}
// Root classes get bonus method implementations if they don't have
// them already. These apply before category replacements.
if (cls->isRootMetaclass()) {
// root metaclass
addMethod(cls, @selector(initialize), (IMP)&objc_noop_imp, "", NO);
}
// Attach categories.
// 分类方法处理
if (previously) {
if (isMeta) {
objc::unattachedCategories.attachToClass(cls, previously,
ATTACH_METACLASS);
} else {
// When a class relocates, categories with class methods
// may be registered on the class itself rather than on
// the metaclass. Tell attachToClass to look for those.
objc::unattachedCategories.attachToClass(cls, previously,
ATTACH_CLASS_AND_METACLASS);
}
}
objc::unattachedCategories.attachToClass(cls, cls,
isMeta ? ATTACH_METACLASS : ATTACH_CLASS);
#if DEBUG
// Debug: sanity-check all SELs; log method list contents
for (const auto& meth : rw->methods()) {
if (PrintConnecting) {
_objc_inform("METHOD %c[%s %s]", isMeta ? '+' : '-',
cls->nameForLogging(), sel_getName(meth.name));
}
ASSERT(sel_registerName(sel_getName(meth.name)) == meth.name);
}
#endif
}
- 方法列表排序
通过 ro
获取 baseMethods
方法列表,然后再进行 prepareMethodLists
方法排序,如果是运行时,将排序好的方法列表插入 rwe
中,查看方法排序的源码如下
static void
prepareMethodLists(Class cls, method_list_t **addedLists, int addedCount,
bool baseMethods, bool methodsFromBundle)
{
...
// Add method lists to array.
// Reallocate un-fixed method lists.
// The new methods are PREPENDED to the method list array.
for (int i = 0; i < addedCount; i++) {
method_list_t *mlist = addedLists[I];
ASSERT(mlist);
// Fixup selectors if necessary
if (!mlist->isFixedUp()) {
fixupMethodList(mlist, methodsFromBundle, true/*sort*/);
}
}
...
}
进入 fixupMethodList
源码实现,如下
static void
fixupMethodList(method_list_t *mlist, bool bundleCopy, bool sort)
{
runtimeLock.assertLocked();
ASSERT(!mlist->isFixedUp());
// fixme lock less in attachMethodLists ?
// dyld3 may have already uniqued, but not sorted, the list
if (!mlist->isUniqued()) {
mutex_locker_t lock(selLock);
// Unique selectors in list.
for (auto& meth : *mlist) {
const char *name = sel_cname(meth.name);
meth.name = sel_registerNameNoLock(name, bundleCopy);
}
}
// Sort by selector address.
if (sort) {
// 根据sel地址排序
method_t::SortBySELAddress sorter;
std::stable_sort(mlist->begin(), mlist->end(), sorter);
}
// Mark method list as uniqued and sorted
mlist->setFixedUp();
}
可以看到方法的排序是按照 selector address
排序的
验证方法排序
- 在
methodizeClass
方法中添加自定义逻辑(目的是断到我们要研究的类,排除其他类和元类的干扰),并添加断点,运行objc-781
源码
- 来到断点,打印当前方法列表
- 进入
prepareMethodLists
开始方法排序流程
- 进入
fixupMethodList
源码,排序
- 来到排序后的断点,打印方法列表
由以上排序方法前后对比,可以验证
methodizeClass
实现了类中方法
的序列化
attachToClass
主要是将分类添加到主类中,其源码实现如下
void attachToClass(Class cls, Class previously, int flags)
{
runtimeLock.assertLocked();
ASSERT((flags & ATTACH_CLASS) ||
(flags & ATTACH_METACLASS) ||
(flags & ATTACH_CLASS_AND_METACLASS));
auto &map = get();
// 找到一个分类进来一次,即一个个加载分类
auto it = map.find(previously);
// 当主类没有实现 load,分类开始加载,迫使主类加载,会走到 if 流程里面
if (it != map.end()) {
category_list &list = it->second;
// 判断是否是元类
if (flags & ATTACH_CLASS_AND_METACLASS) {
int otherFlags = flags & ~ATTACH_CLASS_AND_METACLASS;
// 实例方法
attachCategories(cls, list.array(), list.count(), otherFlags | ATTACH_CLASS);
// 类方法
attachCategories(cls->ISA(), list.array(), list.count(), otherFlags | ATTACH_METACLASS);
} else {
// 如果不是元类,则只走一次 attachCategories
attachCategories(cls, list.array(), list.count(), flags);
}
map.erase(it);
}
}
attachToClass
中的外部循环是找到一个分类就会进到 attachCategories
一次,即找一个就循环一次
attachCategories
主要是准备分类的数据,源码如下
// Attach method lists and properties and protocols from categories to a class.
// Assumes the categories in cats are all loaded and sorted by load order,
// oldest categories first.
static void
attachCategories(Class cls, const locstamped_category_t *cats_list, uint32_t cats_count,
int flags)
{
if (slowpath(PrintReplacedMethods)) {
printReplacements(cls, cats_list, cats_count);
}
if (slowpath(PrintConnecting)) {
_objc_inform("CLASS: attaching %d categories to%s class '%s'%s",
cats_count, (flags & ATTACH_EXISTING) ? " existing" : "",
cls->nameForLogging(), (flags & ATTACH_METACLASS) ? " (meta)" : "");
}
/*
* Only a few classes have more than 64 categories during launch.
* This uses a little stack, and avoids malloc.
*
* Categories must be added in the proper order, which is back
* to front. To do that with the chunking, we iterate cats_list
* from front to back, build up the local buffers backwards,
* and call attachLists on the chunks. attachLists prepends the
* lists, so the final result is in the expected order.
*/
constexpr uint32_t ATTACH_BUFSIZ = 64;
method_list_t *mlists[ATTACH_BUFSIZ];
property_list_t *proplists[ATTACH_BUFSIZ];
protocol_list_t *protolists[ATTACH_BUFSIZ];
uint32_t mcount = 0;
uint32_t propcount = 0;
uint32_t protocount = 0;
bool fromBundle = NO;
bool isMeta = (flags & ATTACH_METACLASS);
// 往`本类` rwe 中`添加属性、方法、协议`等
auto rwe = cls->data()->extAllocIfNeeded();
for (uint32_t i = 0; i < cats_count; i++) {
auto& entry = cats_list[I];
method_list_t *mlist = entry.cat->methodsForMeta(isMeta);
if (mlist) {
if (mcount == ATTACH_BUFSIZ) {
prepareMethodLists(cls, mlists, mcount, NO, fromBundle);
rwe->methods.attachLists(mlists, mcount);
mcount = 0;
}
mlists[ATTACH_BUFSIZ - ++mcount] = mlist;
fromBundle |= entry.hi->isBundle();
}
property_list_t *proplist =
entry.cat->propertiesForMeta(isMeta, entry.hi);
if (proplist) {
if (propcount == ATTACH_BUFSIZ) {
rwe->properties.attachLists(proplists, propcount);
propcount = 0;
}
proplists[ATTACH_BUFSIZ - ++propcount] = proplist;
}
protocol_list_t *protolist = entry.cat->protocolsForMeta(isMeta);
if (protolist) {
if (protocount == ATTACH_BUFSIZ) {
rwe->protocols.attachLists(protolists, protocount);
protocount = 0;
}
protolists[ATTACH_BUFSIZ - ++protocount] = protolist;
}
}
// 如果存在分类
if (mcount > 0) {
prepareMethodLists(cls, mlists + ATTACH_BUFSIZ - mcount, mcount, NO, fromBundle);
rwe->methods.attachLists(mlists + ATTACH_BUFSIZ - mcount, mcount);
if (flags & ATTACH_EXISTING) flushCaches(cls);
}
rwe->properties.attachLists(proplists + ATTACH_BUFSIZ - propcount, propcount);
rwe->protocols.attachLists(protolists + ATTACH_BUFSIZ - protocount, protocount);
}
-
- 在往本类中添加属性、方法、协议时,首先获取
rwe
(因为要对原来的内存进行更改)。extAllocIfNeeded
的源码如下
- 在往本类中添加属性、方法、协议时,首先获取
class_rw_ext_t *extAllocIfNeeded() {
auto v = get_ro_or_rwe();
if (fastpath(v.is())) {
return v.get();
} else {
return extAlloc(v.get());
}
}
判断rwe是否存在,如果存在则直接获取,如果不存在则开辟。extAlloc
的源码如下
class_rw_ext_t *
class_rw_t::extAlloc(const class_ro_t *ro, bool deepCopy)
{
runtimeLock.assertLocked();
// 创建 rwe
auto rwe = objc::zalloc();
rwe->version = (ro->flags & RO_META) ? 7 : 0;
method_list_t *list = ro->baseMethods();
if (list) {
if (deepCopy) list = list->duplicate();
rwe->methods.attachLists(&list, 1);
}
// See comments in objc_duplicateClass
// property lists and protocol lists historically
// have not been deep-copied
//
// This is probably wrong and ought to be fixed some day
property_list_t *proplist = ro->baseProperties;
if (proplist) {
rwe->properties.attachLists(&proplist, 1);
}
protocol_list_t *protolist = ro->baseProtocols;
if (protolist) {
rwe->protocols.attachLists(&protolist, 1);
}
set_ro_or_rwe(rwe, ro);
return rwe;
}
-
- 分类存在时方法处理,这里
mlists
是一个二维数组
mlists[ATTACH_BUFSIZ - ++mcount] = mlist;
可以看到将分类中的方法倒序插入mlists
中分类方法个数大于 0 调用
rwe->methods.attachLists(mlists + ATTACH_BUFSIZ - mcount, mcount);
存入mlists
的末尾
- 分类存在时方法处理,这里
在 for 循环中我们看到有对分类个数判断,
ATTACH_BUFSIZ
的值为 64,即最多允许 64 个分类
attachLists 方法插入
进入 attachLists
方法的源码实现
void attachLists(List* const * addedLists, uint32_t addedCount) {
if (addedCount == 0) return;
if (hasArray()) {
// many lists -> many lists
// 计算数组中旧 lists 的大小
uint32_t oldCount = array()->count;
// 计算新的容量大小
uint32_t newCount = oldCount + addedCount;
// 根据新的容量大小,开辟一个数组,类型是 array_t,通过array()获取
setArray((array_t *)realloc(array(), array_t::byteSize(newCount)));
// 设置新数组的大小
array()->count = newCount;
// 将旧数据(lists)从数组的下标 addedCount 开始存放,大小为旧数据大小 * 单个旧list大小
memmove(array()->lists + addedCount, array()->lists,
oldCount * sizeof(array()->lists[0]));
// 将要添加的数据(addedLists)从数组的下标 0 开始存放,大小为新数据大小 * 单个list大小
memcpy(array()->lists, addedLists,
addedCount * sizeof(array()->lists[0]));
}
else if (!list && addedCount == 1) {
// 0 lists -> 1 list
// list 不存在且 addedCount 个数为 1,此时的 list 是一个一维数组
list = addedLists[0];
}
else {
// 1 list -> many lists
// 获取旧的 list
List* oldList = list;
uint32_t oldCount = oldList ? 1 : 0;
uint32_t newCount = oldCount + addedCount;
// 开辟一个 newCount 容量大小的集合,类型是 array_t,即创建一个数组,放到 array 中,通过 array() 获取
setArray((array_t *)malloc(array_t::byteSize(newCount)));
// 设置数组的大小
array()->count = newCount;
// 判断 old 是否存在,将旧的 list 放入到数组的末尾
if (oldList) array()->lists[addedCount] = oldList;
// 其中 array()->lists 表示首位元素位置
memcpy(array()->lists, addedLists,
addedCount * sizeof(array()->lists[0]));
}
}
从源码中可以看出,插入表主要分为三种情况:
-
多对多:当前
list_array_tt
二维数组中有多个一维数组- 计算数组中
旧 lists
的大小 - 计算新的容量大小(旧数据大小+要添加的数据大小)
- 开辟一个数组,类型是
array_t
,大小为新的容量大小 - 设置数组大小
- 旧数据的内存平移(通过
memmove
后移) - 新数据的存储(通过
memcpy
存储在数组前面)
- 计算数组中
-
0 对 1:当前
list_array_tt
二维数组为空且新增大小数目为 1- 直接赋值
addedList
的第一个元素
- 直接赋值
-
一对多:当前
list_array_tt
二维数组中有一个一维数组- 获取旧的
list
- 计算新的容量 = 旧list个数 + 新lists的个数
- 开辟一个数组,类型是
array_t
,大小为新的容量大小 - 设置数组的大小
- 判断
old
是否存在,将旧的list
放入到数组的末尾 - 从数组起始位置开始存入
新的list
,其中array()->lists
表示首位元素位置
- 获取旧的
对于开发中子类实现父类同名方法、分类同名方法覆盖本类方法,这里也就可以解释了
memmove
内存平移,memcpy(开始位置,放什么,放多大)
memcpy
从原内存地址的起始位置开始拷贝若干个字节到目标内存地址中,速度快
到此,map_images
的加载流程基本上结束了,但是其中的一些细节(如 rwe
的数据加载、分类数据的加载、懒加载与非懒加载的调用流程等)会在后面的文章中详细解析