我们知道dyld是链接各种库,而它本身也是一个库,从它初始化开始到init
,大体流程就是_dyld_start
--> dyldbootstrap::start
--> dyld::_main
--> dyld::initializeMainExecutable
--> ImageLoader::runInitializers
--> ImageLoader::processInitializers
--> ImageLoader::recursiveInitialization
--> doInitialization
-->libSystem_initializer(libSystem.B.dylib)
--> _os_object_init(libdispatch.dylib)
--> _objc_init(libobjc.A.dylib)
具体实现我们后续在说,这是只是通过dyld
引出_objc_init
首先我们查看一下_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?
//读取影响运行时的环境变量,如果需要,还可以打开环境变量帮助 export OBJC_HELP = 1
environ_init();
//关于线程key的绑定,例如线程数据的析构函数
tls_init();
//运行C++静态构造函数,在dyld调用我们的静态析构函数之前,libc会调用_objc_init(),因此我们必须自己做
static_init();
//runtime运行时环境初始化,里面主要是unattachedCategories、allocatedClasses -- 分类初始化
runtime_init();
//初始化libobjc的异常处理系统
exception_init();
//缓存条件初始化
cache_init();
//启动回调机制,通常这不会做什么,因为所有的初始化都是惰性的,但是对于某些进程,我们会迫不及待地加载trampolines dylib
_imp_implementationWithBlock_init();
/*
_dyld_objc_notify_register -- dyld 注册的地方
- 仅供objc运行时使用
- 注册处理程序,以便在映射、取消映射 和初始化objc镜像文件时使用,dyld将使用包含objc_image_info的镜像文件数组,回调 mapped 函数
map_images:dyld将image镜像文件加载进内存时,会触发该函数
load_images:dyld初始化image会触发该函数
unmap_image:dyld将image移除时会触发该函数
*/
_dyld_objc_notify_register(&map_images, load_images, unmap_image);
#if __OBJC2__
didCallDyldNotifyRegister = true;
#endif
}
通过上述代码我们发现,在其中大部分是进行环境
、runtime
、缓存
等的初始化,但是_dyld_objc_notify_register
才是对dyld
进行注册
,通过&
进行赋值,
当我们查看map_images
得知它是在将image
镜像文件加载到内存时进行触发,具体代买如下:
void
map_images_nolock(unsigned mhCount, const char * const mhPaths[],
const struct mach_header * const mhdrs[])
{
if (hCount > 0) {
_read_images(hList, hCount, totalClasses, unoptimizedTotalClasses);
}
}
_read_images
不仅仅是读取了类的信息,通过源码我们可以发现它读取了类
、方法
、协议
、属性
等
1: 条件控制进行一次的加载
2: 修复预编译阶段的 `@selector` 的混乱问题
3: 错误混乱的类处理
4: 修复重映射一些没有被镜像文件加载进来的 类
5: 修复一些消息!
6: 当我们类里面有协议的时候 : readProtocol
7: 修复没有被加载的协议
8: 分类处理
9: 类的加载处理
10 : 没有被处理的类 优化那些被侵犯的类
这里我们只对类的加载进行分析
void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int unoptimizedTotalClasses)
{
if (!doneOnce) {
// namedClasses
//这个表中不包含预先优化的类。
// 4/3是NXMapTable的装载因子
int namedClassesSize =
(isPreoptimized() ? unoptimizedTotalClasses : totalClasses) * 4 / 3;
//创建表(哈希表key-value),目的是查找快
gdb_objc_realized_classes =
NXCreateMapTable(NXStrValueMapPrototype, namedClassesSize);
ts.log("IMAGE TIMES: first time tasks");
}
// Category discovery MUST BE Late to avoid potential races
// when other threads call the new category code before
// this thread finishes its fixups.
// +load handled by prepare_load_methods()
// Realize non-lazy classes (for +load methods and static instances)
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);
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);
}
}
ts.log("IMAGE TIMES: realize non-lazy classes");
Class cls = (Class)classlist[i];//此时获取的cls只是一个地址
Class newCls = readClass(cls, headerIsBundle, headerIsPreoptimized); //读取类,经过这步后,cls获取的值才是一个名字
//经过调试,并未执行if里面的流程
//初始化所有懒加载的类需要的内存空间,但是懒加载类的数据现在是没有加载到的,连类都没有初始化
// 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();
}
通过上述代码我们得知readClass
、realizeClassWithoutSwift
才是重点,readClass
读取了相关类的信息,realizeClassWithoutSwift
是对类进行实现
readClass
Class readClass(Class cls, bool headerIsBundle, bool headerIsPreoptimized)
{
//增加一个监测,当类名为我们当前研究的类的话在进行分析,避免过多系统类扰乱思路
const char *LGPersonName = "LGPerson";
if (strcmp(mangledName, LGPersonName)==0) {
printf("%s -诶唷不错!- %s \n",__func__,mangledName);
}
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);
addClassTableEntry(cls);
}
return cls;
通过上述代码我们发现返回的类信息由原先的地址
通过addNamedClass
关联到了名字
,这样更改直观的查看相关信息
realizeClassWithoutSwift
如果是懒加载类,只有当第一次调用时才会进行触发,想要直接触发的话,可以在类中实现+load
方法,这样就可以调用realizeClassWithoutSwift
方法
接下来我们查看realizeClassWithoutSwift
方法实现
static Class realizeClassWithoutSwift(Class cls, Class previously)
{
//虽然这里进行了ro与rw的赋值,但是实际运行时这里并没有走
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<class_rw_t>();
rw->set_ro(ro);
rw->flags = RW_REALIZED|RW_REALIZING|isMeta;
cls->setData(rw);
//这里通过递归的方式对父类和元类以及根类进行实现
supercls = realizeClassWithoutSwift(remapClass(cls->superclass), nil);
metacls = realizeClassWithoutSwift(remapClass(cls->ISA()), nil);
// Attach categories
methodizeClass(cls, previously);
}
static void methodizeClass(Class cls, Class previously)
{
//在这里进行方法列表的绑定,
// Install methods and properties that the class implements itself.
method_list_t *list = ro->baseMethods();
if (list) {
//在进行prepareMethodLists方法之前,方法列表是无序的
prepareMethodLists(cls, &list, 1, YES, isBundleClass(cls));
if (rwe) rwe->methods.attachLists(&list, 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
}
通过打印我们可得知,在进行prepareMethodLists
方法之前,方法列表存储是无序
的
static void
prepareMethodLists(Class cls, method_list_t **addedLists, int addedCount,
bool baseMethods, bool methodsFromBundle)
{
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*/);
}
}
static void
fixupMethodList(method_list_t *mlist, bool bundleCopy, bool sort)
{
if (sort) {
method_t::SortBySELAddress sorter;
std::stable_sort(mlist->begin(), mlist->end(), sorter);
}
方法通过地址进行了排序(SortBySELAddress
)
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);
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(cls, list.array(), list.count(), flags);
}
map.erase(it);
}
}
上面我们通过调试发现
attachCategories
正常情况下并不会走,但是里面打印的话却走了根据流程分析得知,load_categories_nolock
是肯定的会走的,但是attachCategories
调用的地方都没有,故而全局搜索发现在loadAllCategories
也调用了,并且也会调试到,而loadAllCategories
方法是在load_images
时调用的所以能够反推出流程:反推路径为:
attachCategories
-> load_categories_nolock
-> loadAllCategories
-> load_images
分类加载正常的流程的路径为:
realizeClassWithoutSwift
-> methodizeClass
-> attachToClass
->attachCategories
void
load_images(const char *path __unused, const struct mach_header *mh)
{
if (!didInitialAttachCategories && didCallDyldNotifyRegister) {
didInitialAttachCategories = true;
loadAllCategories();
}
static void
attachCategories(Class cls, const locstamped_category_t *cats_list, uint32_t cats_count,
int flags)
{
//在这里对rwe进行初始化赋值,如果只有本类的情况下,通过cls就可以获取data()数据对rw与ro赋值,而如果添加完分类之后,这时就需要对rwe进行初始化赋值,保证原先rw的稳定,不去对其进行影响
constexpr uint32_t ATTACH_BUFSIZ = 64;
auto rwe = cls->data()->extAllocIfNeeded();
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();
}
//这里是对方法进行倒序插入,更方便读取
class_rw_ext_t *
class_rw_t::extAlloc(const class_ro_t *ro, bool deepCopy)
{
runtimeLock.assertLocked();
auto rwe = objc::zalloc<class_rw_ext_t>();
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);
}
class_rw_ext_t *extAllocIfNeeded() {
auto v = get_ro_or_rwe();
if (fastpath(v.is<class_rw_ext_t *>())) {
return v.get<class_rw_ext_t *>();
} else {
return extAlloc(v.get<const class_ro_t *>());
}
}
通过调试发现,只有当加载分类
时,才会调用extAlloc
,进而对rwe
进行赋值
,这样保证不会对本类
的rw
进行污染
类与分类的加载时机
类和分类搭配使用,其数据的加载时机总结如下:
【情况1】
非懒加载类
+非懒加载分类
,其数据的加载在load_images
方法中,首先对类
进行加载,然后把分类
的信息贴到类中【情况2】
非懒加载类
+懒加载分类
,其数据加载在read_image
就加载数据,数据来自data()
,data()
在编译
时期就已经完成,即data()
中除了类的数据
,还有分类的数据
,与类绑定在一起【情况3】
懒加载类
+懒加载分类
,其数据加载推迟到 第一次消息
时,数据同样来自data()
【情况4】
懒加载类
+非懒加载分类
,只要分类
实现了load
,会迫使
主类提前
加载,即在_read_images
中不会对类做实现操作,需要在load_images
方法中触发类
的数据加载,即rwe初始化
,同时加载分类
数据
具体调试流程没有贴出来,可以通过在.m文件
中实现
与屏蔽load
方法进行打印尝试