本系列博客是本人的源碼閱讀筆記猖毫,如果有 iOS 開發(fā)者在看 runtime 的,歡迎大家多多交流释簿。
前言
load 方法中的最后一個函數(shù):
_dyld_objc_notify_register(&map_2_images, load_images, unmap_image);
告訴我們蕉斜,第一步是 map_2_images,第二步就是 load_images怕膛。筆者前面的文章也已經(jīng)分析過熟嫩,將文件或者類從 mach-o 里讀出來后,進(jìn)行 remap 等操作后放入相應(yīng)的 hashmap 中褐捻,其實是做了第一步:緩存掸茅,為了方便后面調(diào)用。而第二步 load_images柠逞,則是調(diào)用 load 方法昧狮。本文將給大家慢慢揭開 load 方法的神秘面紗。
分析
load_images 方法的實現(xiàn)如下:
void load_images(const char *path __unused, const struct mach_header *mh) {
if (!hasLoadMethods((const headerType *)mh)) return;
prepare_load_methods((const headerType *)mh);
call_load_methods();
}
去掉一些無關(guān)邏輯(主要是鎖操作和注釋)可以看出其實該方法就是兩部操作:
- 調(diào)用 load 方法之前的一些準(zhǔn)備工作
- 開始調(diào)用 load 方法
大家可能會有一些困惑
- load 方法之前的 prepare板壮,究竟 prepare 的是什么逗鸣?為什么要準(zhǔn)備?
- 調(diào)用的大概順序是什么绰精?比如類 A 和 類 A 的父類都實現(xiàn)了 load 方法撒璧,甚至還有其 category 都實現(xiàn)了,那順序是怎么樣的笨使?下面筆者帶大家抽絲剝繭看代碼卿樱。
準(zhǔn)備操作
prepare_load_methods 的代碼如下:
void prepare_load_methods(const headerType *mhdr)
{
size_t count, i;
runtimeLock.assertWriting();
classref_t *classlist =
_getObjc2NonlazyClassList(mhdr, &count);
for (i = 0; i < count; i++) {
schedule_class_load(remapClass(classlist[i]));
}
category_t **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
realizeClass(cls);
assert(cls->ISA()->isRealized());
add_category_to_loadable_list(cat);
}
}
相信看過之前文章的讀者應(yīng)該對
_getObjc2NonlazyClassList
這個方法很熟悉了,作用就是獲取所有擁有 load 方法的類(class)硫椰。
而
_getObjc2NonlazyCategoryList
同理可證殿如,是獲取所有擁有 load 方法的 分類(category)。
所以這是第一步:取出所有的 load 方法的類或者分類最爬。
至此我們開篇提出的幾個問題涉馁,估計大家可能應(yīng)該有一點答案了:
- 比如類 A 和 類 A 的父類都實現(xiàn)了 load 方法,甚至還有其 category 都實現(xiàn)了爱致,那順序是怎么樣的烤送?
因為先加載_getObjc2NonlazyClassList
后加載_getObjc2NonlazyCategoryList
所以,感覺上應(yīng)該是先調(diào)用當(dāng)前類的 load 方法糠悯,再調(diào)用 category 的 load 方法帮坚,那事實是不是這樣呢?我們繼續(xù)分析代碼互艾。
上面不管是 _getObjc2NonlazyClassList
還是_getObjc2NonlazyCategoryList
后面都有接下來的操作试和,我們對這兩個后面的代碼做分析:
for (i = 0; i < count; i++) {
schedule_class_load(remapClass(classlist[i]));
}
這段,先 remap 再調(diào)用 schedule_class_load
所以纫普,remap 大家很熟悉了不做過多介紹了阅悍。我們進(jìn)入 schedule_class_load
看一下:
/***********************************************************************
* prepare_load_methods
* Schedule +load for classes in this image, any un-+load-ed
* superclasses in other images, and any categories in this image.
**********************************************************************/
// Recursively schedule +load for cls and any un-+load-ed superclasses.
// cls must already be connected.
static void schedule_class_load(Class cls)
{
if (!cls) return;
assert(cls->isRealized()); // _read_images should realize
if (cls->data()->flags & RW_LOADED) return;
// Ensure superclass-first ordering
schedule_class_load(cls->superclass);
add_class_to_loadable_list(cls);
cls->setInfo(RW_LOADED);
}
從以上代碼可以看出來,其實 schedule_class_load
只是將 cls 通過一個方法add_class_to_loadable_list
加到一個列表里,等一下!為什么又調(diào)用了自身:
// Ensure superclass-first ordering
schedule_class_load(cls->superclass);
是不是意味著节视,先調(diào)用父類的 load 方法拳锚,再調(diào)用自身的?寻行!
繼續(xù)分析方法 add_class_to_loadable_list
:
void add_class_to_loadable_list(Class cls)
{
IMP method;
loadMethodLock.assertLocked();
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());
}
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++;
}
我們可以發(fā)現(xiàn)霍掺,最終將所有擁有 load 方法的類都加到靜態(tài)對象 loadable_classes
中:
// List of classes that need +load called (pending superclass +load)
// This list always has superclasses first because of the way it is constructed
static struct loadable_class *loadable_classes = nil;
static int loadable_classes_used = 0;
static int loadable_classes_allocated = 0;
類似的 category 的也有類似的方法:
/***********************************************************************
* add_category_to_loadable_list
* Category cat's parent class exists and the category has been attached
* to its class. Schedule this category for +load after its parent class
* becomes connected and has its own +load method called.
**********************************************************************/
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++;
}
以及靜態(tài)變量:
// List of categories that need +load called (pending parent class +load)
static struct loadable_category *loadable_categories = nil;
static int loadable_categories_used = 0;
static int loadable_categories_allocated = 0;
總結(jié)
如果后面的調(diào)用方法和按我們這里加載進(jìn)類列表的順序一致的話,我們可以得出如下結(jié)論:
調(diào)用的大概順序是:比如類 A 和 類 A 的父類都實現(xiàn)了 load 方法拌蜘,甚至還有其 category 都實現(xiàn)了杆烁,那順序是先調(diào)用 A 的父類的 load 方法,再調(diào)用他的或者他自己的 category 的 load 方法简卧。
這可能就是 load 方法調(diào)用時機(jī)問題连躏。