leetcode21:合併兩個有序連結串列
阿新 • • 發佈:2020-08-13
分類的原理
Category編譯之後的底層結構是struct category_t,裡面儲存著分類的物件方法、類方法、屬性、協議資訊
在程式執行的時候,runtime會將Category的資料,合併到類資訊中(類物件、元類物件中)
category_t的底層結構:
struct category_t { const char *name; classref_t cls; struct method_list_t *instanceMethods; struct method_list_t *classMethods; struct protocol_list_t *protocols; struct property_list_t *instanceProperties; // Fields below this point are not always present on disk. struct property_list_t *_classProperties; method_list_t *methodsForMeta(bool isMeta) { if (isMeta) return classMethods; else return instanceMethods; } property_list_t *propertiesForMeta(bool isMeta, struct header_info *hi); };
分類的載入過程:
-
通過Runtime載入某個類的所有Category資料
-
把所有Category的方法、屬性、協議資料,合併到一個大陣列中
後面參與編譯的Category資料,會在陣列的前面 -
將合併後的分類資料(方法、屬性、協議),插入到類原來資料的前面
category_list *cats = unattachedCategoriesForClass(cls, true /*realizing*/); //載入所有分類
attachCategories(cls, cats, false /*don't flush caches*/);//載入分類資料
static void attachCategories(Class cls, category_list *cats, bool flush_caches) { //...省略 auto rw = cls->data(); prepareMethodLists(cls, mlists, mcount, NO, fromBundle); rw->methods.attachLists(mlists, mcount);//載入方法 free(mlists); if (flush_caches && mcount > 0) flushCaches(cls); rw->properties.attachLists(proplists, propcount);//載入屬性 free(proplists); rw->protocols.attachLists(protolists, protocount);//載入協議 free(protolists); }
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]));//複製到陣列前面 }
+load方法
+load方法會在runtime載入類、分類時呼叫,每個類,分類的+load方法只會呼叫一次
呼叫順序
- 先呼叫類的load(先編譯,先呼叫),呼叫子類的load之前先呼叫父類的load
- 呼叫分類的load(先編譯,先呼叫)
原始碼(有所精簡):
load_images(const char *path __unused, const struct mach_header *mh)
{
//...
// Discover load methods
{
rwlock_writer_t lock2(runtimeLock);
prepare_load_methods((const headerType *)mh);//做一些載入前的準備
}
// Call +load methods (without runtimeLock - re-entrant)
call_load_methods();//呼叫load方法
}
void prepare_load_methods(const headerType *mhdr)
{
//...
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);//新增分類方法
}
}
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 call_load_methods(void)
{
//....
do { // 1. 呼叫類的load方法
while (loadable_classes_used > 0){
call_class_loads();
}
// 2. 呼叫分類的方法,僅一次
more_categories = call_category_loads();
} while (loadable_classes_used > 0 || more_categories);
load方法是通過函式直接呼叫
static void call_class_loads(void)
{
int i;
//...
// Call all +loads for the detached list.
for (i = 0; i < used; i++) {
Class cls = classes[i].cls;
load_method_t load_method = (load_method_t)classes[i].method;
if (!cls) continue;
if (PrintLoading) {
_objc_inform("LOAD: +[%s load]\n", cls->nameForLogging());
}
//通過函式指標直接呼叫
(*load_method)(cls, SEL_load);
}
// Destroy the detached list.
if (classes) free(classes);
}
+initialize方法
+initialize方法會在類第一次接收到訊息時呼叫
呼叫順序:
先呼叫父類的+initialize,再呼叫子類的+initialize
(先初始化父類,再初始化子類,每個類只會初始化1次)
+initialize通過objc_msgSend進行呼叫所以有如下特點:
如果子類沒有實現+initialize,會呼叫父類的+initialize(所以父類的+initialize可能會被呼叫多次)
如果分類實現了+initialize,就覆蓋類本身的+initialize呼叫
面試題
- load、initialize方法的區別什麼?
-
呼叫方式
- load是根據函式地址直接呼叫
- initialize是通過objc_msgSend呼叫
-
呼叫時刻
- load是runtime載入類、分類的時候呼叫(只會呼叫1次)
- initialize是類第一次接收到訊息的時候呼叫,每一個類只會initialize一次(父類的initialize方法可能會被呼叫多次)
- load、initialize的呼叫順序?
-
load
-
先呼叫類的load
a) 先編譯的類,優先呼叫load
b) 呼叫子類的load之前,會先呼叫父類的load -
再呼叫分類的load
a) 先編譯的分類,優先呼叫load
-
-
initialize
- 先初始化父類
- 再初始化子類(可能最終呼叫的是父類的initialize方法)