分析之前先引入一张图
dyld在进行类加载时是由
map_images
与load——images
这两大方法来进行加载的,而这两大方法是由objc_init
->_dyld_objc_notify_register
这两个流程方法进行加载的
objc_init()
void _objc_init(void)
{
static bool initialized = false;
if (initialized) return;
initialized = true;
//读取影响运⾏时的环境变量。如果需要,还可以打印环境变量帮助
environ_init();
// 关于线程key的绑定 - ⽐如每线程数据的析构函数
tls_init();
//运⾏C ++静态构造函数。在dyld调⽤我们的静态构造函数之前,`libc` 会调⽤ _objc_init()
static_init();
//runtime运⾏时环境初始化,⾥⾯主要是:unattachedCategories,allocatedClasses
runtime_init();
// 初始化libobjc的异常处理系统
exception_init();
#if __OBJC2__
// 缓存条件初始化
cache_t::init();
#endif
_imp_implementationWithBlock_init();
_dyld_objc_notify_register(&map_images, load_images, unmap_image);
#if __OBJC2__
didCallDyldNotifyRegister = true;
#endif
}
由上述代码可知,在_objc_init
方法中进行了一系列的初始化,而我们类的加载时存在_dyld_objc_notify_register(&map_images, load_images, unmap_image);
这个方法中
_dyld_objc_notify_register dyld注册
应用程序加载时会调用objc_init()
,当执行_dyld_objc_notify_register注册函数
时,会将三个方法注册到dyld
中。
void _dyld_objc_notify_register(_dyld_objc_notify_mapped mapped,
_dyld_objc_notify_init init, _dyld_objc_notify_unmapped unmapped);
- map_images:这里传入的是方法引用,也就是方法的实现地址。管理文件中和动态库中所有文件,如
类class
、协议protocol
、方法selector
、分类category
的实现。 - load_images:该方法传入的是值,即方法实现。加载执行
+load方法
。 - unmap_image:dyld将image移除时,会触发该函数
【问题】由_dyld_objc_notify_register
这个方法我们看到传入了两个方法_dyld_objc_notify_register
和load_images
,但是为什么map_images
传入方法时为什么添加&
这个符号呢?
1.是因为这个属于指针拷贝,这个地方需要内部调用的这个函数,与原函数同步发生变化。map_images
此函数需要映射整个镜像,是一个比较耗时的过程,如果没有发生同步变化,会发生错乱
2.而load_images
没有使用取地址符是因为,此方法只是简单的load
方法的调用,没必要进行同步变化
read_images流程分析
map_images
方法的主要作用是将Mach-O
中的类信息
加载到内存中
,在进入到map_images通过一步步探索最终来到_read_images方法
map_images -> map_images_nolock -> _read_images
read_images主要进行了以下的操作
- 1: 条件控制进⾏⼀次的加载
- 2: 修复预编译阶段的
@selector
的混乱问题 - 3: 错误混乱的类处理
- 4:修复重映射⼀些没有被镜像⽂件加载进来的类
- 5: 修复⼀些消息!
- 6: 当我们类⾥⾯有协议的时候 :
readProtocol
- 7: 修复没有被加载的协议
- 8: 分类处理
- 9: 类的加载处理
- 10 : 没有被处理的类 优化那些被侵犯的类
接下来我们着重的分析几个重点流程
1.主要流程分析
1: 条件控制进⾏⼀次的加载
当doneOnce
为No
时,进入if
语句,紧接着将doneOnce
变为yes
,所以这个判断只进入一次
if (!doneOnce) {
doneOnce = YES;
launchTime = YES;
....省略部分代码
int namedClassesSize =
(isPreoptimized() ? unoptimizedTotalClasses : totalClasses) * 4 / 3;
gdb_objc_realized_classes =
NXCreateMapTable(NXStrValueMapPrototype, namedClassesSize);
}
这里会创建一个哈希表gdb_objc_realized_classes
,所有的类将放入这个表中,目的是方便快捷查找类。gdb_objc_realized_classes
是命名类并且不在dyld共享缓存中,无论是否实现。
2.修复预编译阶段的 @ selector
的混乱问题
// sel 名字 + 地址
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;
}
}
}
}
这里的sel是一个带有地址的字符串
sel名字可能相同,但是地址会出现不同,这个时候需要统一进行修复
【问题】为什么相同的类,方法名相同,但是方法的地址不同,按理说,方法名与方法的地址应该都相同。
假设我们在系统中按照Foundation-> CoreFoundation->AVFoundation这个顺序加载
【答案】是因为我么整个系统中会存在多个库,例如:Foundation 、CoreFoundation
等,每个框架中的每个类基本都会存在retain
方法,当执行该方法时,需要将方法平移到程序出口的位置执行,Foundation框架中的ratain
方法,位置为0,CoreFoundation
位置则为CoreFoundation + 0
的大小,因此方法地址的不同,方法需要平移调整
3、错误混乱的类处理
这里是从Mach_O中取出所有的类进行遍历
for (EACH_HEADER) {
if (! mustReadClasses(hi, hasDyldRoots)) {
// Image is sufficiently optimized that we need not call readClass()
continue;
}
//编译后从类列表中取出所有类,即从Mach-O中的__objc_classlist静态段中取出
classref_t const *classlist = _getObjc2ClassList(hi, &count);
bool headerIsBundle = hi->isBundle();
bool headerIsPreoptimized = hi->hasPreoptimizedClasses();
for (i = 0; i < count; i++) {
//此时cls只是一个地址
Class cls = (Class)classlist[I];
// 此时cls 地址+名字
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;
}
}
}
由上图可知cls被赋予名称时通过readClass
方法,接下来我们看下readClass
方法
Class readClass(Class cls, bool headerIsBundle, bool headerIsPreoptimized)
{
const char *mangledName = cls->nonlazyMangledName();
//调试专用
if (strcmp(mangledName, "LGPerson") == 0)
{
printf("%s LGPerson....\n",__func__);
}
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->setSuperclass(nil);
return nil;
}
cls->fixupBackwardDeployingStableSwift();
Class replacing = nil;
if (mangledName != nullptr) {
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));
// Manually set address-discriminated ptrauthed fields
// so that newCls gets the correct signatures.
newCls->setSuperclass(cls->getSuperclass());
newCls->initIsa(cls->getIsa());
rw->set_ro((class_ro_t *)newCls->data());
newCls->setData(rw);
freeIfMutable((char *)old_ro->getName());
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(mangledName == nullptr || getClassExceptSomeSwift(mangledName));
} else {
if (mangledName) { //some Swift generic classes can lazily generate their names
addNamedClass(cls, mangledName, replacing);
} else {
Class meta = cls->ISA();
const class_ro_t *metaRO = meta->bits.safe_ro();
ASSERT(metaRO->getNonMetaclass() && "Metaclass with lazy name must have a pointer to the corresponding nonmetaclass.");
ASSERT(metaRO->getNonMetaclass() == cls && "Metaclass nonmetaclass pointer must equal the original class.");
}
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;
}
- 通过
nonlazyMangledName
获取类名
const char *nonlazyMangledName() const {
return bits.safe_ro()->getName();
}
const class_ro_t *safe_ro() const {
class_rw_t *maybe_rw = data();
if (maybe_rw->flags & RW_REALIZED) {
// maybe_rw 是 rw
return maybe_rw->ro();
} else {
// maybe_rw 实际上时ro
return (class_ro_t *)maybe_rw;
}
}
这里获取非懒加载的类名
如果rw
中存在这从rw
中取,反之从ro
中获取
- 通过
addNamedClass
方法,将当前类添加到已经创建好的gdb_objc_realized_classes
哈希表,也就是read_Images第一步,doneOnce只加载一次时创建的哈希表
,该表用于存放所有类
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 {
//添加到gdb_objc_realized_classes哈希表
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
方法中的objc::allocatedClasses
在objc_init()中runtime_init()
方法中出现过,allocatedClasses.init
进行内存中类的表创建。
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);
}
所以综上所述,readClass
的主要作用就是将Mach-O中
的类读取到内存,即插入表中,但是目前的类仅有两个信息:地址以及名称,而mach-O的其中的data数据还未读取出来,所以这个时候的cls
可以认为只是一个空房子
,房子
里面的家具什么的
还没有搬入进入(方法、属性、协议等)
4. 类的加载处理
// +load handled by prepare_load_methods()
// Realize non-lazy classes (for +load methods and static instances)
for (EACH_HEADER) {
classref_t const *classlist = hi->nlclslist(&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
}
const char *mangledName = cls->nonlazyMangledName();
if (strcmp(mangledName, "LGPerson") == 0)
{
printf("%s LGPerson....\n",__func__);
}
realizeClassWithoutSwift(cls, nil);
}
}
ts.log("IMAGE TIMES: realize non-lazy classes");
在这个方法中,当我们类实现了load
方法是,会进入以上方法,即 非懒加载
,在这个方法中最重要的方法当属realizeClassWithoutSwift
这个方法,此方法我们下一篇在讲解