1. 程式人生 > 實用技巧 >leetcode21:合併兩個有序連結串列

leetcode21:合併兩個有序連結串列

分類的原理

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);
};

分類的載入過程:

  1. 通過Runtime載入某個類的所有Category資料

  2. 把所有Category的方法、屬性、協議資料,合併到一個大陣列中
    後面參與編譯的Category資料,會在陣列的前面

  3. 將合併後的分類資料(方法、屬性、協議),插入到類原來資料的前面

    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方法只會呼叫一次

呼叫順序

  1. 先呼叫類的load(先編譯,先呼叫),呼叫子類的load之前先呼叫父類的load
  2. 呼叫分類的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呼叫

面試題

  1. load、initialize方法的區別什麼?
  • 呼叫方式

    • load是根據函式地址直接呼叫
    • initialize是通過objc_msgSend呼叫
  • 呼叫時刻

    • load是runtime載入類、分類的時候呼叫(只會呼叫1次)
    • initialize是類第一次接收到訊息的時候呼叫,每一個類只會initialize一次(父類的initialize方法可能會被呼叫多次)
  1. load、initialize的呼叫順序?
  • load

    1. 先呼叫類的load
      a) 先編譯的類,優先呼叫load
      b) 呼叫子類的load之前,會先呼叫父類的load

    2. 再呼叫分類的load
      a) 先編譯的分類,優先呼叫load

  • initialize

  1. 先初始化父類
  2. 再初始化子類(可能最終呼叫的是父類的initialize方法)