在 iOS 应用程序加载 一篇,我们得知,app
由内核引导启动,之后交由dyld
主导,完成运行环境的初始化,配合ImageLoader
将二进制文件按格式加载到内存,动态链接依赖库,并由runtime
负责加载成objc
定义的结构,所有初始化工作结束之后,dyld
调用应用程序的main
函数。
其中,dyld
与 objc
互相配合,dyld
加载动态库的过程中初始化 objc
,objc
在初始化的过程中注册回调函数 _dyld_objc_notify_register
,通知dyld
执行 map_images
,load_images
,unmap_images
以完成映射、载入等过程(详细过程参见 iOS 应用程序加载 )。
这一设计是很有必要的,一来分工明确,各司其职;二来runtime
接管了这部分工作,可以实现runtime
的动态性。接下来我们结合 libobjc
的初始化来探究 iOS 类的加载过程。
0 . 从 _objc_init 开始
void _objc_init(void)
{
static bool initialized = false;
if (initialized) return;
initialized = true;
// fixme defer initialization until an objc-using image is found?
environ_init(); // 读取影响运行时的环境变量
tls_init(); // 处理线程key的绑定
static_init(); // 运行C++静态构造函数
runtime_init(); // runtime运行时环境初始化,里面主要是:unattachedCategories,allocatedClasses
exception_init(); // 初始化libobjc的异常处理系统
cache_init(); // 缓存条件初始化
_imp_implementationWithBlock_init();
_dyld_objc_notify_register(&map_images, load_images, unmap_image);
#if __OBJC2__
didCallDyldNotifyRegister = true;
#endif
}
在任何 image
初始化之前,要先确保 libSystem_initializer
的正常执行,它是优先级最高的,必须先保证系统库的正常初始化,之后 经由 libdispatch_init
--> _os_object_init
继而调用 _objc_init
。
在 _objc_init 中:
environ_init()
读取影响运行时的环境变量.tls_init()
处理线程key的绑定,处理每个线程数据的析构函数.static_init()
运行C ++静态构造函数,在dyld调用我们的静态构造函数之前,libc
会调用 _objc_init(), 因此我们必须自己做.runtime_init()
runtime运行时环境初始化,里面主要是:unattachedCategories(尚未附加到本类的分类表),allocatedClasses(用objc_allocateClassPair分配的所有类(和元类)的表) 两个表的创建.exception_init()
初始化libobjc的异常处理系统以监测异常.cache_init()
缓存条件初始化_imp_implementationWithBlock_init()
启动 trampoline machinery。通常这不会做什么,因为所有的初始化都是惰性的,但是对于某些进程,我们会迫不及待地加载trampolines dylib。
_objc_init 完成上述的 init 工作之后,通知 dyld
在适当的时机调用 map_images
, load_images
, unmap_image
函数。
1. map_images
map_images
的工作是:处理由 dyld
映射的给定镜像。负责管理文件和动态库中所有的符号(class、selector、protocol、category 等)
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
函数中调用 map_images_nolock
,(这里只关注读取镜像的逻辑,所以做了一些源码的省略处理。)
void
map_images_nolock(unsigned mhCount, const char * const mhPaths[],
const struct mach_header * const mhdrs[])
{
......
// 从objc的元数据查找所有的镜像
......
if (hCount > 0) {
_read_images(hList, hCount, totalClasses, unoptimizedTotalClasses);
}
for (auto func : loadImageFuncs) {
for (uint32_t i = 0; i < mhCount; i++) {
func(mhdrs[I]);
}
}
}
从主程序的 【machO】 中找到所有的镜像文件,调用 _read_images
,执行所有的类注册和修复等功能。在所有设置完成后调用镜像加载。那么在加载镜像之前的操作就聚集在了 _read_images
里面了,这也是我们重点研究的对象。源码很长,精简如下:
1.1 _read_images
void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int unoptimizedTotalClasses)
{
......
// ① 控制进行只做一次加载(建表:未在dyld共享缓存中的已命名类的列表,无论是否实现)
if (!doneOnce) {
doneOnce = YES;
int namedClassesSize =
(isPreoptimized() ? unoptimizedTotalClasses : totalClasses) * 4 / 3;
gdb_objc_realized_classes =
NXCreateMapTable(NXStrValueMapPrototype, namedClassesSize);
}
......
// ② 修复@selector 引用(修复预编译阶段的 `@selector` 的混乱问题)
static size_t UnfixedSelectors;
{
mutex_locker_t lock(selLock);
for (EACH_HEADER) {
if (hi->hasPreoptimizedSelectors()) continue;
bool isBundle = hi->isBundle();
SEL *sels = _getObjc2SelectorRefs(hi, &count);
UnfixedSelectors += count;
for (i = 0; i < count; i++) {
const char *name = sel_cname(sels[i]);
SEL sel = sel_registerNameNoLock(name, isBundle);
if (sels[i] != sel) {
sels[i] = sel;
}
}
}
}
// ③ 修复未解析的 future class, 标记 Bundle class
for (EACH_HEADER) {
classref_t const *classlist = _getObjc2ClassList(hi, &count);
for (i = 0; i < count; i++) {
Class cls = (Class)classlist[I];
Class newCls = readClass(cls, headerIsBundle, headerIsPreoptimized);
if (newCls != cls && newCls) {
resolvedFutureClasses = (Class *)
realloc(resolvedFutureClasses,
(resolvedFutureClassCount+1) * sizeof(Class));
resolvedFutureClasses[resolvedFutureClassCount++] = newCls;
}
}
}
// ④ 修复重映射的类;(一些没有被镜像文件加载进来的类)
if (!noClassesRemapped()) {
for (EACH_HEADER) {
Class *classrefs = _getObjc2ClassRefs(hi, &count);
for (i = 0; i < count; i++) {
remapClassRef(&classrefs[I]);
}
classrefs = _getObjc2SuperRefs(hi, &count);
for (i = 0; i < count; i++) {
remapClassRef(&classrefs[I]);
}
}
}
#if SUPPORT_FIXUP
// ⑤ Fix up old objc_msgSend_fixup call sites
for (EACH_HEADER) {
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);
}
}
#endif
// ⑥ readProtocol
NXMapTable *protocol_map = protocols();
protocol_t * const *protolist = _getObjc2ProtocolList(hi, &count);
for (EACH_HEADER) {
for (i = 0; i < count; i++) {
readProtocol(protolist[i], cls, protocol_map,
isPreoptimized, isBundle);
}
}
// ⑦ Fix up @protocol references
for (EACH_HEADER) {
if (launchTime && cacheSupportsProtocolRoots && hi->isPreoptimized())
continue;
protocol_t **protolist = _getObjc2ProtocolRefs(hi, &count);
for (i = 0; i < count; i++) {
remapProtocolRef(&protolist[I]);
}
}
// ⑧ load_categories_nolock
if (didInitialAttachCategories) {
for (EACH_HEADER) {
load_categories_nolock(hi);
}
}
// ⑨ non-lazy 类的加载
for (EACH_HEADER) {
classref_t const *classlist =
_getObjc2NonlazyClassList(hi, &count);
for (i = 0; i < count; i++) {
Class cls = remapClass(classlist[i]);
if (!cls) continue;
addClassTableEntry(cls);
realizeClassWithoutSwift(cls, nil);
}
}
// ⑩ realize future classes (没有被处理的类,)
// Realize newly-resolved future classes, in case CF manipulates them 实现新解析的未来类,以防CF操作它们
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);
}
if (DebugNonFragileIvars) {
realizeAllClasses();
}
}
简化总结如下:
- ①:条件控制只做一次:
建表gdb_objc_realized_classes
,存储未在dyld共享缓存中的已命名类的列表,无论是否实现- ②:修复@selector 引用:
修复预编译阶段 @selector 的混乱问题,我们知道,SEL 是一个带地址的字符串,尽管两个方法的名字相同,但方法的地址未必相同。如上,可能多个框架都会有init方法
,在系统中读取方法的地址是按照框架在主程中的偏移以及方法在框架内的偏移来定位的。因此,我们需要对@selector进行适当调整。- ③:修复未解析的 future classes.
从【MachO】__objc_classlist
section中加载类列表,遍历列表中的类,进行readClass
,如果readClass
的结果与列表中的类不同,则进行修复操作,但这一般不会出现,只有类被移动并且没有被删除才会出现。在readClass
中,从cls->mangledName()
中取到类的名字,将类的名字与地址关联,插入到第①步创建的gdb_objc_realized_classes
中,同时将该类以及元类插入到allocatedClasses
表。
在这一步中,readClass
将类的地址和名字
加载到了内存中。- ④:修复重映射的类:
如果存在没有被镜像文件加载进来的类,则在此时进行重映射。- ⑤:修复一些旧的
objc_msgSend_fixup
调用
主要对一些旧的消息修复进行强制修复工作,如alloc -> objc_alloc、allocWithZone -> objc_allocWithZone 等。- ⑥:readProtocol:
创建存储proctol的哈希表,从【MachO】__objc_protolist
section中读取协议列表,遍历协议列表,进行readProtocol
,将协议添加到proctol表,注册到内存。- ⑦: Fix up @protocol references
预先优化的镜像可能已经有了正确的protocol引用,但是我们并不能确定,所以这里进行一次修复工作,以防被引用的协议被重新分配。- ⑧:load_categories
加载分类,需要注意的是,这里并不会加载分类,只有在didInitialAttachCategories
赋值为true之后才会执行,而didInitialAttachCategories
赋值为true的过程是在_dyld_objc_notify_register
的调用完成后的第一个load_images
调用时赋值的。- ⑨:non-lazy 类的加载
这里完成的是非懒加载的类的实现,也就是实现了+load
方法的类。
从【MachO】__objc_nlclslist
section中读取非懒加载类列表,执行addClassTableEntry(cls);
将非懒加载类插入到类表,加载到内存。在第③步中,我们加载到内存的类已有了地址和名字
,最后执行realizeClassWithoutSwift
对类的结构进行完善。- ⑩:realize future classes
如果存在被处理的 future class ,则需要在这里实现,以防CF操作它们。这里的实现也是通过realizeClassWithoutSwift
。
_read_images
大致做了上述这些事情,如果只关注类的加载过程的话,可以作如下简化理解:
- 1.建表
gdb_objc_realized_classes
创建一个不在共享缓存的已命名类的表,不关乎这个类是否实现。 - 2.
readClass
:
从【MachO】__objc_classlist
section 中读取 类的列表数据获取类的地址信息,将类的名字写入,插入gdb_objc_realized_classes
表,并且插入allocatedClasses
表。自此,类被载入内存,并且有了地址与名字
。 - 3.
realizeClassWithoutSwift
实现类的细节(ro
、rw
等处理),保证类的结构的完整性。
具体的readClass
和 realizeClassWithoutSwift
代码分析如下:
1.1.1 readClass
Class readClass(Class cls, bool headerIsBundle, bool headerIsPreoptimized)
{
const char *mangledName = cls->mangledName();
// 如果继承链中存在父类缺失或者weak-linked情况,直接忽略这个类, return nil
if (missingWeakSuperclass(cls)) {
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();
Class replacing = nil;
// 如果这个类是一个早先分配的作为将来要处理的类,那么将objc_class数据复制到future class,保存future的rw数据块
if (Class newCls = popFutureNamedClass(mangledName)) {
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;
}
// 如果是预优化的类 并且 不是future class, 则 ASSERT,
if (headerIsPreoptimized && !replacing) {
ASSERT(getClassExceptSomeSwift(mangledName));
} else {
// 关联地址与名字 加入gdb_objc_realized_classes表
addNamedClass(cls, mangledName, replacing);
// 加入allocatedClasses表
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;
}
readClass
处理:
- 1.从
cls->mangledName()
获取类的名字 - 2.如果继承链中存在父类缺失或者weak-linked情况,直接忽略这个类(这个类是不完整的), return nil;
- 3.如果这个类是一个早先分配的作为将来要处理的类,那么将objc_class数据复制到future class,保存future的
rw
数据块; - 4.如果是预优化的类 并且 不是future class, 则 ASSERT;否则
执行addNamedClass
关联类的地址与名字 ,插入gdb_objc_realized_classes
表.
执行addClassTableEntry
插入allocatedClasses
表;
1.1.1.1 addNamedClass
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));
}
判断gdb_objc_realized_classes
表中是否已经存在该名称的类,如果存在,插入nonMetaClasses
表。如果不存在插入gdb_objc_realized_classes
表。
1.1.1.2 addClassTableEntry
static void
addClassTableEntry(Class cls, bool addMeta = true)
{
runtimeLock.assertLocked();
auto &set = objc::allocatedClasses.get();
ASSERT(set.find(cls) == set.end());
if (!isKnownClass(cls))
set.insert(cls);
if (addMeta)
addClassTableEntry(cls->ISA(), false);
}
如果该类是在运行时已知的类(如位于共享缓存中,在一个已加载镜像的数据段中,或者已经用obj_allocateClassPair分配),则不需要插入表,否则插入allocatedClasses
表,同时将类的元类也插入表中。allocatedClasses
表是在runtime_init()
时创建的.
至此,【MachO】中的类已载入内存,并且有了名字和地址,但是数据还没有关联,而数据的关联是在realizeClassWithoutSwift
中进行的。
1.1.2 realizeClassWithoutSwift
static Class realizeClassWithoutSwift(Class cls, Class previously)
{
runtimeLock.assertLocked();
class_rw_t *rw;
Class supercls;
Class metacls;
if (!cls) return nil;
if (cls->isRealized()) return cls; // 如果类已实现,则直接返回
ASSERT(cls == remapClass(cls));
auto ro = (const class_ro_t *)cls->data(); // 从data()中取类的信息
auto isMeta = ro->flags & RO_META;
if (ro->flags & RO_FUTURE) {
// This was a future class. rw data is already allocated.
rw = cls->data();
ro = cls->data()->ro();
ASSERT(!isMeta);
cls->changeInfo(RW_REALIZED|RW_REALIZING, RW_FUTURE);
} else {
// Normal class. Allocate writeable class data.
rw = objc::zalloc();
rw->set_ro(ro);
rw->flags = RW_REALIZED|RW_REALIZING|isMeta;
cls->setData(rw);
}
#if FAST_CACHE_META
if (isMeta) cls->cache.setBit(FAST_CACHE_META);
#endif
cls->chooseClassArrayIndex();
if (PrintConnecting) {
_objc_inform("CLASS: realizing class '%s'%s %p %p #%u %s%s",
cls->nameForLogging(), isMeta ? " (meta)" : "",
(void*)cls, ro, cls->classArrayIndex(),
cls->isSwiftStable() ? "(swift)" : "",
cls->isSwiftLegacy() ? "(pre-stable swift)" : "");
}
supercls = realizeClassWithoutSwift(remapClass(cls->superclass), nil);
metacls = realizeClassWithoutSwift(remapClass(cls->ISA()), nil);
#if SUPPORT_NONPOINTER_ISA
...... NONPOINTER_ISA的一些处理
#endif
// Update superclass and metaclass in case of remapping
cls->superclass = supercls;
cls->initClassIsa(metacls);
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();
}
}
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);
}
// Attach categories
methodizeClass(cls, previously);
return cls;
}
将realizeClassWithoutSwift
做简化理解如下:
从
cls->data()
中读取类的信息,如果是 future class ,对rw
、ro
赋值;如果是正常的类,则开辟rw
空间,对ro
进行赋值,并拷贝到rw
中。递归实现类的父类以及元类,随后更新该类的父类和元类以备重新映射。确保继承链以及isa链的完整性。
从
ro
中赋值一些标志到rw
中。如果父类存在,则将这个类链接到它的父类的子类列表,否则作为一个新的根类。
执行
methodizeClass
1.1.2.1 methodizeClass
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();
// 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.
method_list_t *list = ro->baseMethods();
if (list) {
prepareMethodLists(cls, &list, 1, YES, isBundleClass(cls));
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);
}
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 {
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
}
methodizeClass
方法中,主要做了这些事情:
- ① install 类的方法列表
(method_list_t)
;- ② install 类的属性列表
(property_list_t)
;- ③ install 类的协议列表
(protocol_list_t)
;- ④ Attach categories.
- ① install 类的方法列表
(method_list_t)
method_list_t *list = ro->baseMethods();
if (list) {
prepareMethodLists(cls, &list, 1, YES, isBundleClass(cls));
if (rwe) rwe->methods.attachLists(&list, 1);
}
从ro
中读取baseMethods()
,如果列表有值,通过prepareMethodLists
对方法列表进行预处理
static void
prepareMethodLists(Class cls, method_list_t **addedLists, int addedCount,
bool baseMethods, bool methodsFromBundle)
{
runtimeLock.assertLocked();
if (addedCount == 0) return;
if (baseMethods) {
ASSERT(cls->hasCustomAWZ() && cls->hasCustomRR() && cls->hasCustomCore());
}
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*/);
}
}
if (cls->isInitialized()) {
objc::AWZScanner::scanAddedMethodLists(cls, addedLists, addedCount);
objc::RRScanner::scanAddedMethodLists(cls, addedLists, addedCount);
objc::CoreScanner::scanAddedMethodLists(cls, addedLists, addedCount);
}
}
对于某些类的方法会存在 RR/AWZ/Core
这样的特殊情况,但是我们不需要做任何处理,因为默认的 RR/AWZ/Core
是不会在setInitialized()
方法执行之前设置的。
之后通过fixupMethodList
对方法列表进行排序,这也是我们在进行方法查找流程中对方法列表进行二分查找的前提:保证方法列表中的方法是有序的。
static void
fixupMethodList(method_list_t *mlist, bool bundleCopy, bool sort)
{
runtimeLock.assertLocked();
ASSERT(!mlist->isFixedUp());
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) {
method_t::SortBySELAddress sorter;
std::stable_sort(mlist->begin(), mlist->end(), sorter);
}
// Mark method list as uniqued and sorted
mlist->setFixedUp();
}
fixup
做了两件事:Unique
和 Sort
.
Unique:
保证方法的唯一性;
Sort:
对方法列表进行排序:排序的过程是根据方法的name
进行排序;struct SortBySELAddress : public std::binary_function
{ bool operator() (const method_t& lhs, const method_t& rhs) { return lhs.name < rhs.name; } }; 最后标记方法列表为已修复状态,后续不需再进行
fixup
了。
prepareMethodLists
的工作完成后,判断rwe
是否存在,如果存在,则rwe->methods.attachLists
。而此时rwe
是不存在的,这里补充说明一下:
Tips:
在 iOS 类的结构分析 这篇文章中,我们已知:类在编译期时,类的一些数据信息保存在
ro
中,包含了类的名称,方法,协议,实例变量等编译期确定
的信息。当类被Runtime加载之后,runtime会为它分配额外的用于 读取/写入 的rw
。
ro
是只读的,存放的是编译期间就确定
的字段信息;而rw
是在 runtime 时才创建的,它会先将ro
的内容拷贝一份,再将类的分类、属性、方法、协议等信息添加进去。而对于存在动态更改行为的类,会将这部分动态的内容提取到
rwe
中。
到目前为止,类的加载过程,对于 ro
, rw
均已赋值完毕,而 rwe
的赋值是要在类存在动态更改行为时触发的。此时rwe
还不存在,所以并不需要rwe->methods.attachLists
。那么rwe
是何时被赋值的呢?
class_rw_ext_t *extAllocIfNeeded() {
auto v = get_ro_or_rwe();
if (fastpath(v.is())) {
return v.get();
} else {
return extAlloc(v.get());
}
}
rwe
的创建是通过extAllocIfNeeded
。搜索extAllocIfNeeded
可得到下面的结果。
分别对应了:attachCategories
, demangledName
,class_setVersion
,addMethod
,addMethods
,class_addProtocol
,_class_addProperty
,objc_duplicateClass
。无一不是动态修改的行为。
如果rwe
已创建,且方法列表有值,attachLists(&list, 1)
- ② install 类的属性列表
(property_list_t)
property_list_t *proplist = ro->baseProperties;
if (rwe && proplist) {
rwe->properties.attachLists(&proplist, 1);
}
如果rwe
已创建,且属性列表有值,attachLists(&proplist, 1)
- ③ install 类的协议列表
(protocol_list_t)
protocol_list_t *protolist = ro->baseProtocols;
if (rwe && protolist) {
rwe->protocols.attachLists(&protolist, 1);
}
如果rwe
已创建,且协议列表有值,attachLists(&protolist, 1)
可见只要满足条件,都会执行attachLists
函数
void attachLists(List* const * addedLists, uint32_t addedCount) {
if (addedCount == 0) return;
if (hasArray()) {
// many lists -> many lists
uint32_t oldCount = array()->count;
uint32_t newCount = oldCount + addedCount;
setArray((array_t *)realloc(array(), array_t::byteSize(newCount)));
array()->count = newCount;
memmove(array()->lists + addedCount, array()->lists,
oldCount * sizeof(array()->lists[0]));
memcpy(array()->lists, addedLists,
addedCount * sizeof(array()->lists[0]));
}
else if (!list && addedCount == 1) {
// 0 lists -> 1 list
list = addedLists[0];
}
else {
// 1 list -> many lists
List* oldList = list;
uint32_t oldCount = oldList ? 1 : 0;
uint32_t newCount = oldCount + addedCount;
setArray((array_t *)malloc(array_t::byteSize(newCount)));
array()->count = newCount;
if (oldList) array()->lists[addedCount] = oldList;
memcpy(array()->lists, addedLists,
addedCount * sizeof(array()->lists[0]));
}
}
attachLists
函数区分了三种情况,我们按照执行流程分析如下:
- 【第一种】0 lists --> 1 list
从无到有的过程,lists
中还未有任何内容,此时直接将 new list 内容赋值过来即可。- 【第二种】1 list -> many lists
此时lists
中已有【第一种】 中赋值的数组。要添加的大小与原大小相加计算出所需容量大小,根据容量开辟array()
空间并赋值:将旧的内容(oldList
)放在数组末尾,新的内容(addedLists
)从lists
的首地址开始,大小为addedCount
个空间,拷贝到lists
中,这样,旧数据放最后,新数据整体放在前。- 【第三种】many lists -> many lists
在 【第二种】 中,array()
已赋值,所以当有新的数组attach
时,重新开辟array()
空间,大小依然是旧数组容量与新数据容量之和。将旧数据移动到最后,新数据整体拷贝到旧数据的前面。
- ④ Attach categories
// Attach categories.
if (previously) {
if (isMeta) {
objc::unattachedCategories.attachToClass(cls, previously,
ATTACH_METACLASS);
} else {
objc::unattachedCategories.attachToClass(cls, previously,
ATTACH_CLASS_AND_METACLASS);
}
}
objc::unattachedCategories.attachToClass(cls, cls,
isMeta ? ATTACH_METACLASS : ATTACH_CLASS);
Attach categories
是添加分类的过程,此部分内容放在 iOS 分类详解 中探讨。
至此为止,类的加载过程(除分类以外)大致完成,通过上面这些步骤,类的结构已大致完备。
通过 readClass
将类的地址与名字
进行关联并且载入内存;
通过 realizeClassWithoutSwift
对类的 ro
,rw
完成赋值;
通过 methodizeClass
对类的方法、属性、协议进行完备,并且完成分类的加载。
当存在动态修改等行为时,也会将类的动态修改部分存入rwe
。
这些都是在 map_images
中完成的。下面我们接着研究load_images
。
2. load_images
当镜像文件映射完毕后,会继而执行 load_images
,处理在dyld
中已映射完毕的镜像的 +load
方法.
void
load_images(const char *path __unused, const struct mach_header *mh)
{
if (!didInitialAttachCategories && didCallDyldNotifyRegister) {
didInitialAttachCategories = true;
loadAllCategories();
}
if (!hasLoadMethods((const headerType *)mh)) return;
recursive_mutex_locker_t lock(loadMethodLock);
// Discover load methods
{
mutex_locker_t lock2(runtimeLock);
prepare_load_methods((const headerType *)mh);
}
// Call +load methods (without runtimeLock - re-entrant)
call_load_methods();
}
首先,如果 libobjc.A.dylib 动态库的镜像文件映射完毕且首次执行load_images
的时候,会调用loadAllCategories()
函数,完成所有分类的加载。(分类相关的内容本章暂不探讨,放在 iOS分类详解 里统一探讨);
系统依赖的动态库的很多,每一个动态库在执行load_images
函数的时候都会判断当前动态库的【MachO】中__objc_nlclslist
和__objc_nlcatlist
列表是否有内容。没有直接return。如果有,执行prepare_load_methods
2.1 prepare_load_methods
void prepare_load_methods(const headerType *mhdr)
{
size_t count, I;
runtimeLock.assertLocked();
// 类的load
classref_t const *classlist =
_getObjc2NonlazyClassList(mhdr, &count);
for (i = 0; i < count; i++) {
schedule_class_load(remapClass(classlist[i]));
}
// 分类的load
category_t * const *categorylist = _getObjc2NonlazyCategoryList(mhdr, &count);
for (i = 0; i < count; i++) {
category_t *cat = categorylist[I];
Class cls = remapClass(cat->cls);
if (!cls) continue; // category for ignored weak-linked class
if (cls->isSwiftStable()) {
_objc_fatal("Swift class extensions and categories on Swift "
"classes are not allowed to have +load methods");
}
realizeClassWithoutSwift(cls, nil);
ASSERT(cls->ISA()->isRealized());
add_category_to_loadable_list(cat);
}
}
prepare_load_methods
做了两件事:
- 从当前动态库的【MachO】header中读取
__objc_nlclslist
,执行schedule_class_load
;
- 从当前动态库的【MachO】header中读取
__objc_nlcatlist
,随后迫使主类实现,继而执行add_category_to_loadable_list
,将待执行+load
的分类添加到loadable_categories
表中。
2.1.1 schedule_class_load
static void schedule_class_load(Class cls)
{
if (!cls) return;
ASSERT(cls->isRealized()); // _read_images should realize
// 如已执行add_class_to_loadable_list 则不需继续添加
if (cls->data()->flags & RW_LOADED) return;
// 确保父类先执行
schedule_class_load(cls->superclass);
// 插表
add_class_to_loadable_list(cls);
// 设置标记:cls已执行add_class_to_loadable_list
cls->setInfo(RW_LOADED);
}
对于不存在的类或者未实现的类进行拦截,同时对于已执行过add_class_to_loadable_list
函数的类也进行过滤。当满足条件向下执行的时候,要先确保该类的父类先于该类执行+load
(如果父类实现了+load
),随后执行add_class_to_loadable_list
插表操作,并且标记cls
为已完成插表操作。
2.1.1.1 add_class_to_loadable_list
void add_class_to_loadable_list(Class cls)
{
IMP method;
loadMethodLock.assertLocked();
// 取到要存入loadable_classes表的类 +load方法的 IMP
method = cls->getLoadMethod();
if (!method) return; // Don't bother if cls has no +load method
if (PrintLoading) {
_objc_inform("LOAD: class '%s' scheduled for +load",
cls->nameForLogging());
}
// 空表或者达最大容量,则开辟或扩容 ,每个动态库中的所有+load方法会存储在同一个表中
if (loadable_classes_used == loadable_classes_allocated) {
loadable_classes_allocated = loadable_classes_allocated*2 + 16;
loadable_classes = (struct loadable_class *)
realloc(loadable_classes,
loadable_classes_allocated *
sizeof(struct loadable_class));
}
loadable_classes[loadable_classes_used].cls = cls;
loadable_classes[loadable_classes_used].method = method;
loadable_classes_used++;
}
通过getLoadMethod()
取cls
中的ro()->baseMethods()
,遍历查询load
方法,找到则返回方法的 IMP,执行插表操作,未找到直接return
.
每个动态库中需要执行+load
的类会存入到同一张表loadable_classes
,根据已占用空间和开辟空间进行对比,以判断是够需要扩容重新开辟空间。
2.1.2 add_category_to_loadable_list
void add_category_to_loadable_list(Category cat)
{
IMP method;
loadMethodLock.assertLocked();
method = _category_getLoadMethod(cat);
// Don't bother if cat has no +load method
if (!method) return;
if (PrintLoading) {
_objc_inform("LOAD: category '%s(%s)' scheduled for +load",
_category_getClassName(cat), _category_getName(cat));
}
if (loadable_categories_used == loadable_categories_allocated) {
loadable_categories_allocated = loadable_categories_allocated*2 + 16;
loadable_categories = (struct loadable_category *)
realloc(loadable_categories,
loadable_categories_allocated *
sizeof(struct loadable_category));
}
loadable_categories[loadable_categories_used].cat = cat;
loadable_categories[loadable_categories_used].method = method;
loadable_categories_used++;
}
分类和主类的插表操作大致相同的,通过_category_getLoadMethod
从分类的cat->classMethods
中查找+load
方法,找到则插入loadable_categories
表。
这里需要注意的是在执行add_category_to_loadable_list
操作之前,先要完成主类的实现realizeClassWithoutSwift
。
2.2 call_load_methods()
prepare_load_methods
函数,将类的+load,父类的+load,分类的+load,都以准备完毕,接下来就要执行call_load_methods()
。
void call_load_methods(void)
{
static bool loading = NO;
bool more_categories;
loadMethodLock.assertLocked();
// Re-entrant calls do nothing; the outermost call will finish the job.
if (loading) return;
loading = YES;
void *pool = objc_autoreleasePoolPush();
do {
// 1. Repeatedly call class +loads until there aren't any more
while (loadable_classes_used > 0) {
call_class_loads();
}
// 2. Call category +loads ONCE
more_categories = call_category_loads();
// 3. Run more +loads if there are classes OR more untried categories
} while (loadable_classes_used > 0 || more_categories);
objc_autoreleasePoolPop(pool);
loading = NO;
}
进入do....while
循环,从先前存入的表数据loadable_classes
中取出可执行+load
方法的类数据,进行消息发送,执行+load
方法,从loadable_categories
表中取出可执行+load
方法的分类数据,进行消息发送,执行+load
方法。
这样,+load
方法得以被调用。
3. 总结
本篇文章主要是从_objc_init
开始,结合map_images
、load_images
来探究类的加载过程。 如果你对 dyld 还不是很熟悉,建议先看一看我的另一篇文章 iOS 应用程序加载 ,再来看这篇文章的时候会更清晰些。
尽管类的加载细节是相同的,但加载的【时机】却不尽相同,为此有必要在这里也补充一下 懒加载类 与 非懒加载类。
补充:懒加载类与非懒加载类
非懒加载类:随着程序启动,在map_iamges
阶段既完成加载的类.
懒加载类:当第一次使用类时才加载的类(通常为第一次发送消息时)。
我们可以通过实现+load
方法来迫使类提前加载,即非懒加载类。
对于几种实现+load方法的情况的堆栈情况如下图:
- 主类实现
+load
当主类实现+load
的时候,在_read_images
的第⑨步中就会实现类。
- 子类实现
+load
当子类实现+load
时,上图堆栈中会执行两次realizeClassWithoutSwift
,如果前面探讨的内容你有仔细看的话,应该记得,在realizeClassWithoutSwift
方法内,为了保证继承链的完整性,会递归调用realizeClassWithoutSwift
,去实现父类。
- 分类实现
+load
分类实现+load
时,在prepare_load_methods
时,会先调用realizeClassWithoutSwift
,确保本类先实现。所以堆栈信息是这样的。
- 均未实现
+load
当上述+load
均未实现的时候,类的实现将直到第一次使用它时,这里的情况一般就是发送消息的时候了。在消息的慢速查找流程中,如果类还没有实现,为了保证程序的正常运行,会在这里实现类,这也是符合正常逻辑的。
才疏学浅,文章如有误导之处,还请指正,若觉得对你有所帮助,也可以给我一个赞 感谢!