??首先我們都知道在iOS應用啟動的時候會調用所有類和其分類的+load方法。子類的load方法會在父類方法執行完成之后執行,分類的+load會在主類執行之后執行。不可繼承,子類沒有實現的時候,文件加載的時候是不會調用父類的load方法的。那么為什么+load的方法會有這樣的特性,runtime又有哪些巧妙的處理呢。今天我們來刨根問底一下+load方法。
??首先解釋兩個變量和兩個結構體,可以先看一眼有個印象,后面遇到再回來仔細察看
//每次調用add_class_to_loadable_list()方法都會++,記錄這個方法的調用次數,也相當于Class中+load方法列表的個數
static int loadable_classes_used = 0;
//同上,只不過對應的是add_category_to_loadable_list()方法和Category中的+load方法列表
static int loadable_categories_used = 0;
//存儲了+load方法所屬的Class和+load方法的IMP
struct loadable_class {
Class cls; // may be nil
IMP method;
};
//同上,只不過對應的是Category
struct loadable_category {
Category cat; // may be nil
IMP method;
};
//存儲Class和categoty這兩個struct數據的是數組
1. Category方法列表的裝載
1.1 調用棧
??我們還是通過runtime的源碼來分析這個問題首先看分類的方法是什么時候加到主類的methed_list_t列表中的。下圖為調用棧:??可以看到調用次序是這樣的,我們重點看_read_images方法。
map_images() —>map_images_nolock()—>_read_images()
1.2 _read_images()
??源碼較長,這里我只貼對我們有用的部分
void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int unoptimizedTotalClasses)
{
//前面的都省略
// Discover categories.
for (EACH_HEADER) {
category_t **catlist =
_getObjc2CategoryList(hi, &count);
bool hasClassProperties = hi->info()->hasCategoryClassProperties();
for (i = 0; i < count; i++) {
category_t *cat = catlist[i];
Class cls = remapClass(cat->cls);
if (!cls) {
// Category's target class is missing (probably weak-linked).
// Disavow any knowledge of this category.
catlist[i] = nil;
if (PrintConnecting) {
_objc_inform("CLASS: IGNORING category \?\?\?(%s) %p with "
"missing weak-linked target class",
cat->name, cat);
}
continue;
}
// Process this category.
// First, register the category with its target class.
// Then, rebuild the class's method lists (etc) if
// the class is realized.
bool classExists = NO;
if (cat->instanceMethods || cat->protocols
|| cat->instanceProperties)
{
addUnattachedCategoryForClass(cat, cls, hi);
if (cls->isRealized()) {
remethodizeClass(cls);
classExists = YES;
}
if (PrintConnecting) {
_objc_inform("CLASS: found category -%s(%s) %s",
cls->nameForLogging(), cat->name,
classExists ? "on existing class" : "");
}
}
if (cat->classMethods || cat->protocols
|| (hasClassProperties && cat->_classProperties))
{
addUnattachedCategoryForClass(cat, cls->ISA(), hi);
if (cls->ISA()->isRealized()) {
remethodizeClass(cls->ISA());
}
if (PrintConnecting) {
_objc_inform("CLASS: found category +%s(%s)",
cls->nameForLogging(), cat->name);
}
}
}
}
//后面的也省略
}
??注意到里面的addUnattachedCategoryForClass方法了嗎,OC里面都喜歡這種見文知義的命名方法,直譯過來就是為類添加未附加的類別。在這個方法里面,會找到未添加的類別列表對Class和Category做一個映射關聯。而在remethodizeClass()方法里面會調用attachCategories把Category中的方法列表加到Class的methed_list_t里面去。而且是插入到Class方法列表的前面(這就是Category中重寫主類的方法導致的方法覆蓋的原因)
2. +load方法的調用
2.1 調用棧
??我們接著來看+load的方法的調用,如下圖所示,這些會在Category方法裝載之后!下圖為調用棧
2.2 load_images()
??load_images()方法會多次調用(每個類都會調用一次)。我們來看load_images()方法的具體內容
void
load_images(const char *path __unused, const struct mach_header *mh)
{
// Return without taking locks if there are no +load methods here.
if (!hasLoadMethods((const headerType *)mh)) return;
recursive_mutex_locker_t lock(loadMethodLock);
// Discover load methods
{
rwlock_writer_t lock2(runtimeLock);
prepare_load_methods((const headerType *)mh);
}
// Call +load methods (without runtimeLock - re-entrant)
call_load_methods();
}
??里面有prepare_load_methods()和call_load_methods()這兩個主要方法,先來看第一個
2.3 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]));
}
#pragma mark - 我是分割線—————————————————————————————————
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);
}
}
??這里面又分兩個重要的部分,上面分割線之前是Class中的+load方法加入到對應的list中去,那怎么保證的父類先調用呢
2.3.1 schedule_class_load() & add_class_to_loadable_list()
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);
}
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[loadable_classes_used].cls = cls;
loadable_classes[loadable_classes_used].method = method;
loadable_classes_used++;
}
??schedule_class_load()方法做了遞歸調用一直調用到superclass為空,在schedule_class_load()方法中會調用add_class_to_loadable_list。這樣就保證了父類的+load方法是加載到list前面的,從父類到子類依次往數組中添加。執行的時候也是從前往后遍歷數組調用。
??add_class_to_loadable_list()方法的實現,我們可以很清楚的看到loadable_classes的初始化策略與溢出時的擴容策略。每次需要擴容都會在原來的基礎之上*2+16。
2.3.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[loadable_categories_used].cat = cat;
loadable_categories[loadable_categories_used].method = method;
loadable_categories_used++;
}
??我們來看分割線之后的部分,在Class的+load方法處理完成時候才會來處理Category中的+load方法,這里用到了add_category_to_loadable_list()可以看到與add_class_to_loadable_list()方法的內容基本一樣只是標志位和數組的名字不同而已。add_category_to_loadable_list()在Category中的+load方法加入到對應的列表中。至此prepare_load_methods()方法執行完畢。
2.4 call_load_methods()
??接下來我們來看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;
}
??看代碼這里還用到了aotureleasePool,這個是題外話了,撇開不談。我們來看今天的重點,首先是一個do while循環,循環條件有兩個。第一個loadable_classes_used(查看文章開頭),第二個是call_category_loads()方法的返回值,這個返回值是什么呢,其實就是loadable_categories_used>0。在這里先后調用所有Class列表中的+load方法,Category列表中的+load方法call_class_loads()和call_category_loads()兩個方法的代碼我就不貼了。代碼有點多,影響閱讀,有興趣可以去看runtime的源碼。
3. 總結與問題
3.1總結
??分析下來,基本回答了文章開頭所述的+load方法特征的原因。總結如下:
??1、Category方法列表的裝載是在_read_images的時候發生的,這個調用比較早在map_images之后,load_images前。
??2、+load方法的調用是在load_images是發生的,而且load_image會重復調用(每個類都會調用)。而load_images調在_read_images之后,也就是說是在Category中的方法插入到Class中的方法列表之后調用的。
??3、+load方法的調用是又專門的方法負責的,Class和Category分別有一個數組保存+load方法(數組內保存的是loadable_class,和loadable_category結構體參考文章開頭的介紹)。所以不會被Category方法的裝載導致方法覆蓋。
??4、Class的數組,加入的次序也是有保證的,從最高級的父類到子類一次加入,調用的時候能保證先父類后子類。
??5、Category數組,調用是在Class中的方法列表調用完成之后,保證了次序。
3.2問題
??call_class_loads()的實現比較簡單,就是一個for循環依次調用+load。call_category_loads()內部的實現就復雜了不少,還有許多復雜的代碼,筆者也是沒有太看懂內部的那些判斷和循環,也不敢在這里誤人子弟。如果有哪位明白的請不吝指教。