Category:從底層原理研究到面試題分析

image

本文將對category的源碼進行比較全面的整理分析,最后結合一些面試題進行總結锥腻,希望對讀者有所裨益嗦董。
GitHub Repo:iOSDeepAnalyse
Follow: MisterBooo · GitHub
Source: Category:從底層原理研究到面試題分析

目錄

  • 1.Category源碼分析
  • 2.load源碼分析
  • 3.initialize源碼分析
  • 4.load與initialize對比
  • 5.面試題分析

源碼分析

1.源碼閱讀前的準備

本節(jié)代碼基于以下的代碼進行編譯研究:

@interface Person : NSObject
- (void)instanceRun;
+ (void)methodRun;
@property(nonatomic, copy) NSString *name;
@end
@interface Person (Eat)
@property(nonatomic, assign) int age;
- (void)instanceEat;
+ (void)methodEat;
@end

@interface Person (Drink)
- (void)instanceEat;
@property(nonatomic, copy) NSString *waters;
@end

2.objc4中的源碼

通過objc4中的源碼進行分析,可以在objc-runtime-new.h中找到Category的結構如下

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

不難發(fā)現(xiàn)在這個結構體重存儲著對象方法瘦黑、類方法京革、協(xié)議和屬性。接下來我們來驗證一下我們剛剛自己編寫的Person+Eat.m這個分類在編譯時是否是這種結構供璧。

通過

xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc Person+Eat.m

命令將Person+Eat.m文件編譯成cpp文件存崖,以下的源碼分析基于Person+Eat.cpp里面的代碼。下面讓我們開始窺探Category的底層結構吧~

2.Person+Eat.cpp源碼

Person+Eat.cpp的代碼滑到底部部分睡毒,可以看見一個名為_category_t的結構體,這就是Category的底層結構

struct _category_t {
    const char *name;
    struct _class_t *cls;
    const struct _method_list_t *instance_methods; // 對象方法列表
    const struct _method_list_t *class_methods;// 類方法列表
    const struct _protocol_list_t *protocols;// 協(xié)議列表
    const struct _prop_list_t *properties;// 屬性列表
};

Person+Eat.m這個分類的結構也是符合_category_t這種形式的

static struct _category_t _OBJC_$_CATEGORY_Person_$_Eat __attribute__ ((used, section ("__DATA,__objc_const"))) = 
{
    "Person",
    0, // &OBJC_CLASS_$_Person,
    (const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_Person_$_Eat, // 對象方法列表
    (const struct _method_list_t *)&_OBJC_$_CATEGORY_CLASS_METHODS_Person_$_Eat,// 類方法列表
    (const struct _protocol_list_t *)&_OBJC_CATEGORY_PROTOCOLS_$_Person_$_Eat, // 協(xié)議列表
    (const struct _prop_list_t *)&_OBJC_$_PROP_LIST_Person_$_Eat, // 屬性列表
};

我們開始來分析上面這個結構體的內部成員冗栗,其中Person表示類名

對象方法列表

_OBJC_$_CATEGORY_INSTANCE_METHODS_Person_$_Eat是對象方法列表演顾,在Person+Eat.cpp文件中可以找到_OBJC_$_CATEGORY_INSTANCE_METHODS_Person_$_Eat具體描述

_OBJC_$_CATEGORY_INSTANCE_METHODS_Person_$_Eat __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    sizeof(_objc_method),
    1,
    {{(struct objc_selector *)"instanceEat", "v16@0:8", (void *)_I_Person_Eat_instanceEat}}
};

instanceEat就是我們上述實現(xiàn)的Person+Eat分類里面的實例方法。

類方法列表

_OBJC_$_CATEGORY_CLASS_METHODS_Person_$_Eat是類方法列表蔫慧,在Person+Eat.cpp中具體描述如下

 _OBJC_$_CATEGORY_CLASS_METHODS_Person_$_Eat __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    sizeof(_objc_method),
    1,
    {{(struct objc_selector *)"classEat", "v16@0:8", (void *)_C_Person_Eat_classEat}}
};
協(xié)議列表

_OBJC_CATEGORY_PROTOCOLS_$_Person_$_Eat是協(xié)議列表湘捎,在Person+Eat.cpp中具體描述如下

 _OBJC_CATEGORY_PROTOCOLS_$_Person_$_Eat __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    2,
    &_OBJC_PROTOCOL_NSCopying,
    &_OBJC_PROTOCOL_NSCoding
};
屬性列表

_OBJC_$_PROP_LIST_Person_$_Eat是屬性列表盐股,在Person+Eat.cpp中具體描述如下

_OBJC_$_PROP_LIST_Person_$_Eat __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    sizeof(_prop_t),
    2,
    {{"weight","Ti,N"},
    {"height","Ti,N"}}
};

3.Category的加載處理過程

通過上面的分析,我們驗證了編寫一個分類的時候棉钧,在編譯期間,這個分類內部的確會有category_t這種數(shù)據(jù)結構涕蚤,那么這種數(shù)據(jù)結構是如何作用到這個類的呢宪卿?分類的方法和類的方法調用的邏輯是怎么樣的呢的诵?我們接下來回到objc4源碼中進一步分析Category的加載處理過程來揭曉Category的神秘面紗。

我們按照如下函數(shù)的調用順序佑钾,一步一步的研究Category的加載處理過程

void _objc_init(void);
└── void map_images(...);
    └── void map_images_nolock(...);
        └── void _read_images(...);
            └── void _read_images(...);
                └── static void remethodizeClass(Class cls);
                    └──attachCategories(Class cls, category_list *cats, bool flush_caches);
文件名 方法
objc-os.mm _objc_init
objc-os.mm map_images
objc-os.mm map_images_nolock
objc-runtime-new.mm _read_images
objc-runtime-new.mm remethodizeClass
objc-runtime-new.mm attachCategories
objc-runtime-new.mm attachLists

iOS 程序 main 函數(shù)之前發(fā)生了什么
中提到西疤,_objc_init這個函數(shù)是runtime的初始化函數(shù),那我們從_objc_init這個函數(shù)開始進行分析休溶。

/***********************************************************************
* _objc_init
* Bootstrap initialization. Registers our image notifier with dyld.
* Called by libSystem BEFORE library initialization time
**********************************************************************/

void _objc_init(void)
{
    static bool initialized = false;
    if (initialized) return;
    initialized = true;
    
    // fixme defer initialization until an objc-using image is found?
    environ_init();
    tls_init();
    static_init();
    lock_init();
    exception_init();

    _dyld_objc_notify_register(&map_images, load_images, unmap_image);
}

接著我們來到 &map_images讀取資源(images這里代表資源模塊)代赁,來到map_images_nolock函數(shù)中找到_read_images函數(shù),在_read_images函數(shù)中我們找到與分類相關的代碼

// 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) {
                catlist[i] = nil;
                if (PrintConnecting) {
                    _objc_inform("CLASS: IGNORING category \?\?\?(%s) %p with "
                                 "missing weak-linked target class", 
                                 cat->name, cat);
                }
                continue;
            }
            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);
                }
            }
        }
    }

在上面的代碼中兽掰,主要做了以下的事情

  • 1.獲取category列表list
  • 2.遍歷category list中的每一個category
  • 3.獲取categorycls芭碍,如果沒有cls,則跳過(continue)這個繼續(xù)獲取下一個
  • 4.如果cat有實例方法孽尽、協(xié)議豁跑、屬性,則調用addUnattachedCategoryForClass泻云,同時如果cls有實現(xiàn)的話艇拍,就進一步調用remethodizeClass方法
  • 5.如果cat有類方法、協(xié)議宠纯,則調用addUnattachedCategoryForClass卸夕,同時如果cls的元類有實現(xiàn)的話,就進一步調用remethodizeClass方法

其中4,5兩步的區(qū)別主要是cls是類對象還是元類對象的區(qū)別婆瓜,我們接下來主要是看在第4步中的addUnattachedCategoryForClassremethodizeClass方法快集。

addUnattachedCategoryForClass
static void addUnattachedCategoryForClass(category_t *cat, Class cls, 
                                          header_info *catHeader)
{
    runtimeLock.assertWriting();

    // DO NOT use cat->cls! cls may be cat->cls->isa instead
    NXMapTable *cats = unattachedCategories();
    category_list *list;

    list = (category_list *)NXMapGet(cats, cls);
    if (!list) {
        list = (category_list *)
            calloc(sizeof(*list) + sizeof(list->list[0]), 1);
    } else {
        list = (category_list *)
            realloc(list, sizeof(*list) + sizeof(list->list[0]) * (list->count + 1));
    }
    list->list[list->count++] = (locstamped_category_t){cat, catHeader};
    NXMapInsert(cats, cls, list);
}
static NXMapTable *unattachedCategories(void)
{
    runtimeLock.assertWriting();
    //全局對象
    static NXMapTable *category_map = nil;
    if (category_map) return category_map;
    // fixme initial map size
    category_map = NXCreateMapTable(NXPtrValueMapPrototype, 16);
    return category_map;
}

對上面的代碼進行解讀:

  • 1.通過unattachedCategories()函數(shù)生成一個全局對象cats
  • 2.我們從這個單例對象中查找cls ,獲取一個category_list *list列表
  • 3.要是沒有list 指針廉白。那么我們就生成一個category_list 空間个初。
  • 4.要是有list 指針,那么就在該指針的基礎上再分配出category_list 大小的空間
  • 5.在這新分配好的空間猴蹂,將這個catcatHeader 寫入院溺。
  • 6.將數(shù)據(jù)插入到cats 中,keycls, 值是list
remethodizeClass
static void remethodizeClass(Class cls)
{
    //分類數(shù)組
    category_list *cats;
    bool isMeta;

    runtimeLock.assertWriting();

    isMeta = cls->isMetaClass();

    // Re-methodizing: check for more categories
    if ((cats = unattachedCategoriesForClass(cls, false/*not realizing*/))) {
        if (PrintConnecting) {
            _objc_inform("CLASS: attaching categories to class '%s' %s", 
                         cls->nameForLogging(), isMeta ? "(meta)" : "");
        }
        
        attachCategories(cls, cats, true /*flush caches*/);        
        free(cats);
    }
}

remethodizeClass函數(shù)中將通過attachCategories函數(shù)我們的分類信息附加到該類中。

attachCategories
//cls = [Person class]
//cats = [category_t(Eat),category_t(Drink)]
static void 
attachCategories(Class cls, category_list *cats, bool flush_caches)
{
    if (!cats) return;
    if (PrintReplacedMethods) printReplacements(cls, cats);

    bool isMeta = cls->isMetaClass();

    // 重新分配內存
    method_list_t **mlists = (method_list_t **)
        malloc(cats->count * sizeof(*mlists));
    property_list_t **proplists = (property_list_t **)
        malloc(cats->count * sizeof(*proplists));
    protocol_list_t **protolists = (protocol_list_t **)
        malloc(cats->count * sizeof(*protolists));

    // Count backwards through cats to get newest categories first
    int mcount = 0;
    int propcount = 0;
    int protocount = 0;
    int i = cats->count;
    bool fromBundle = NO;
    while (i--) {
        auto& entry = cats->list[i];

        method_list_t *mlist = entry.cat->methodsForMeta(isMeta);
        if (mlist) {
            mlists[mcount++] = mlist;
            fromBundle |= entry.hi->isBundle();
        }

        property_list_t *proplist = 
            entry.cat->propertiesForMeta(isMeta, entry.hi);
        if (proplist) {
            proplists[propcount++] = proplist;
        }

        protocol_list_t *protolist = entry.cat->protocols;
        if (protolist) {
            protolists[protocount++] = protolist;
        }
    }

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

對上面的代碼進行解讀(假設cls是類對象磅轻,元類對象分析同理):

  • 1.根據(jù)方法列表珍逸、屬性列表、協(xié)議列表分配內存

  • 2.cats是這種數(shù)據(jù)結構:[category_t(Eat),category_t(Drink)聋溜,谆膳。。撮躁。]漱病,遍歷cats,然后

    • 1.獲取一個分類里面的所有對象方法,存儲在mlist數(shù)組中杨帽,然后再將mlist數(shù)組添加到二維數(shù)組mlists
    • 2.獲取一個分類里面的所有協(xié)議漓穿,存儲在proplist數(shù)組中,然后再將proplist數(shù)組添加到二維數(shù)組proplists
    • 3.獲取一個分類里面的所有屬性睦尽,存儲在protolist數(shù)組中器净,然后再將protolist數(shù)組添加到二維數(shù)組protolists
  • 3.獲取cls 的的bits 指針 class_rw_t,通過attachLists方法,將mlists附加到類對象方法列表中当凡,將proplists附加到類對象的屬性列表中山害,將protolists附加到類對象的協(xié)議列表中

其中mlists的數(shù)據(jù)結構如下,proplistsprotolists同理:

[
    [method_t,method_t],
    [method_t,method_t]
]
attachLists
   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]));
        }
        else if (!list  &&  addedCount == 1) {
            // 0 lists -> 1 list
            list = addedLists[0];
        } 
        else {
            // 1 list -> many lists
            List* oldList = list;
            uint32_t oldCount = oldList ? 1 : 0;
            uint32_t newCount = oldCount + addedCount;
            setArray((array_t *)malloc(array_t::byteSize(newCount)));
            array()->count = newCount;
            if (oldList) array()->lists[addedCount] = oldList;
            memcpy(array()->lists, addedLists, 
                   addedCount * sizeof(array()->lists[0]));
        }
    }

attachLists方法主要關注兩個變量array()->listsaddedLists

  • array()->lists: 類對象原來的方法列表,屬性列表沿量,協(xié)議列表浪慌,比如Person中的那些方法等
  • addedLists:傳入所有分類的方法列表,屬性列表朴则,協(xié)議列表权纤,比如Person(Eat)、Person(Drink)中的那些方法等乌妒。

上面代碼的作用就是通過memmove將原來的類找那個的方法汹想、屬性、協(xié)議列表分別進行后移撤蚊,然后通過memcpy將傳入的方法古掏、屬性、協(xié)議列表填充到開始的位置侦啸。
我們來總結一下這個過程:

  • 通過Runtime加載某個類的所有Category數(shù)據(jù)

  • 把所有Category的方法槽唾、屬性、協(xié)議數(shù)據(jù)光涂,合并到一個大數(shù)組中庞萍,后面參與編譯的Category數(shù)據(jù),會在數(shù)組的前面

  • 將合并后的分類數(shù)據(jù)(方法忘闻、屬性钝计、協(xié)議),插入到類原來數(shù)據(jù)的前面

我們可以用如下的動畫來表示一下這個過程


image

通過這個動畫我們不難注意到以下兩點:

  • 1)服赎、category的方法沒有“完全替換掉”原來類已經(jīng)有的方法葵蒂,也就是說如果category和原來類都有methodA,那么category附加完成之后重虑,類的方法列表里會有兩個methodA.
  • 2)、category的方法被放到了新方法列表的前面秦士,而原來類的方法被放到了新方法列表的后面缺厉,這也就是我們平常所說的category的方法會“覆蓋”掉原來類的同名方法,這是因為運行時在查找方法的時候是順著方法列表的順序查找的,它只要一找到對應名字的方法提针,就會罷休_命爬,殊不知后面可能還有一樣名字的方法。

我們通過代碼來驗證一下上面兩個注意點是否正確

image

load與initialize

load方法與initialize方法的調用與一般普通方法的調用有所區(qū)別辐脖,因此筆者將其放在這一節(jié)一并分析進行想對比

load源碼分析

同樣的饲宛,我們按照如下函數(shù)的調用順序,一步一步的研究load的加載處理過程

void _objc_init(void);
└── void load_images(...);
    └── void call_load_methods(...);
        └── void call_class_loads(...);

我們直接從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();
}

load_images方法中主要關注prepare_load_methods方法與call_load_methods方法

prepare_load_methods
void prepare_load_methods(header_info *hi)
{
    size_t count, i;

    rwlock_assert_writing(&runtimeLock);

    classref_t *classlist =
        _getObjc2NonlazyClassList(hi, &count);
    for (i = 0; i < count; i++) {
        schedule_class_load(remapClass(classlist[i]));
    }

    category_t **categorylist = _getObjc2NonlazyCategoryList(hi, &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;

    // 確保父類優(yōu)先的順序
    schedule_class_load(cls->superclass);

    add_class_to_loadable_list(cls);
    cls->setInfo(RW_LOADED);
}

顧名思義嗜价,這個函數(shù)的作用就是提前準備好滿足 +load 方法調用條件的類和分類艇抠,以供接下來的調用。
然后在這個類中調用了schedule_class_load(Class cls)方法久锥,并且在入?yún)r對父類遞歸的調用了家淤,確保父類優(yōu)先的順序。

call_load_methods

經(jīng)過prepare_load_methods的準備瑟由,接下來call_load_methods就開始大顯身手了絮重。

void call_load_methods(void)
{
    static BOOL loading = NO;
    BOOL more_categories;

    recursive_mutex_assert_locked(&loadMethodLock);

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

call_load_methods中我們看do循環(huán)這個方法,它調用上一步準備好的類和分類中的 +load 方法歹苦,并且確保類優(yōu)先于分類的順序青伤。

call_class_loads

call_class_loadsload方法調用的核心方法

static void call_class_loads(void)
{
    int i;

    // Detach current loadable list.
    struct loadable_class *classes = loadable_classes;
    int used = loadable_classes_used;
    loadable_classes = nil;
    loadable_classes_allocated = 0;
    loadable_classes_used = 0;

    // 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_internal(classes);
}

這個函數(shù)的作用就是真正負責調用類的 +load 方法了。它從全局變量 loadable_classes 中取出所有可供調用的類殴瘦,并進行清零操作狠角。

loadable_classes = nil;
loadable_classes_allocated = 0;
loadable_classes_used = 0;

其中 loadable_classes 指向用于保存類信息的內存的首地址,loadable_classes_allocated 標識已分配的內存空間大小痴施,loadable_classes_used 則標識已使用的內存空間大小擎厢。

然后,循環(huán)調用所有類的 +load 方法辣吃。注意动遭,這里是(調用分類的 +load 方法也是如此)直接使用函數(shù)內存地址的方式 (*load_method)(cls, SEL_load); 對 +load 方法進行調用的,而不是使用發(fā)送消息 objc_msgSend 的方式神得。

但是如果我們寫[Student load]時厘惦,這是使用發(fā)送消息 objc_msgSend 的方式。

舉個??:

@interface Person : NSObject
@end
@implementation Person
+ (void)load{
    NSLog(@"%s",__func__);
}
@end
@interface Student : Person
@end
@implementation Student
//+ (void)load{
//    NSLog(@"%s",__func__);
//}
@end
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        [Student load];
    }
    return 0;
}

輸出如下:

image

第一句走的是load的加載方式哩簿,而第二句走的是objc_msgSend中消息發(fā)送機制宵蕉,isa指針通過superclass在父類中找到類方法。

小總結

  • +load方法會在runtime加載類节榜、分類時調用
  • 每個類羡玛、分類的+load,在程序運行過程中只調用一次
  • 調用順序

1.先調用類的+load

按照編譯先后順序調用(先編譯宗苍,先調用)
調用子類的+load之前會先調用父類的+load

2.再調用分類的+load

按照編譯先后順序調用(先編譯稼稿,先調用)

initialize源碼分析

同樣的薄榛,我們按照如下函數(shù)的調用順序,一步一步的研究initialize的加載處理過程

Method class_getInstanceMethod(Class cls, SEL sel);
└── IMP lookUpImpOrNil(Class cls, SEL sel, id inst, bool initialize, bool cache, bool resolver);
    └── IMP lookUpImpOrForward(Class cls, SEL sel, id inst, bool initialize, bool cache, bool resolver);
        └── void _class_initialize(Class cls);
              └── void callInitialize(Class cls);

我們直接打開objc-runtime-new.mm文件來研究lookUpImpOrForward這個方法

lookUpImpOrForward
IMP lookUpImpOrForward(Class cls, SEL sel, id inst,
                       bool initialize, bool cache, bool resolver)
{
    ...
        rwlock_unlock_write(&runtimeLock);
    }

    if (initialize  &&  !cls->isInitialized()) {
        _class_initialize (_class_getNonMetaClass(cls, inst));
        // If sel == initialize, _class_initialize will send +initialize and 
        // then the messenger will send +initialize again after this 
        // procedure finishes. Of course, if this is not being called 
        // from the messenger then it won't happen. 2778172
    }

    // The lock is held to make method-lookup + cache-fill atomic 
    // with respect to method addition. Otherwise, a category could 
    ...
}

initialize && !cls->isInitialized()判斷代碼表明當一個類需要初始化卻沒有初始化時让歼,會調用_class_initialize進行初始化敞恋。

_class_initialize
void _class_initialize(Class cls)
{
    ...
    Class supercls;
    BOOL reallyInitialize = NO;

    // Make sure super is done initializing BEFORE beginning to initialize cls.
    // See note about deadlock above.
    supercls = cls->superclass;
    if (supercls  &&  !supercls->isInitialized()) {
        _class_initialize(supercls);
    }
     ...
 
     callInitialize(cls);
     
    ...
}

同樣的supercls && !supercls->isInitialized()表明對入?yún)⒌母割愡M行了遞歸調用,以確保父類優(yōu)先于子類初始化谋右。

callInitialize
void callInitialize(Class cls)
{
    ((void(*)(Class, SEL))objc_msgSend)(cls, SEL_initialize);
    asm("");
}

最后在callInitialize中通過發(fā)送消息 objc_msgSend 的方式對 +initialize方法進行調用,也就是說+ initialize與一般普通方法的調用處理是一樣的硬猫。
舉個??:

@interface Person : NSObject
@end
@implementation Person
+ (void)initialize{
    NSLog(@"%s",__func__);
}
@end
@implementation Person (Eat)
+ (void)initialize{
    NSLog(@"%s",__func__);
}
@end
@interface Student : Person
@end
@implementation Student
+ (void)initialize{
    NSLog(@"%s",__func__);
}
@end
@interface Teacher : Person
@end
@implementation Teacher
@end
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        [Student alloc];
        [Student initialize];
        [Person alloc];
        [Person alloc];
        [Person alloc];
        [Person alloc];
        [Person alloc];
        [Person alloc];
        NSLog(@"****分割線***");
        [Teacher alloc];
        [Teacher initialize];
    }
    return 0;
}

輸出如下:


image

小總結

  • +initialize方法會在類第一次接收到消息時調用
  • 調用順序
  1. 先調用父類的+initialize,再調用子類的+initialize
  2. 先初始化父類改执,再初始化子類啸蜜,每個類只會初始化1次

load與initialize對比

條件 +load +initialize
關鍵方法 (*load_method)(cls, SEL_load) objc_msgSend
調用時機 被添加到 runtime 時 收到第一條消息前,可能永遠不調用
調用順序 父類->子類->分類 父類->子類
調用次數(shù) 1次 多次
是否需要顯式調用父類實現(xiàn)
是否沿用父類的實現(xiàn)
分類中的實現(xiàn) 類和分類都執(zhí)行 覆蓋類中的方法天梧,只執(zhí)行分類的實現(xiàn)

面試題

1.Category的使用場合是什么盔性?
    1. 給現(xiàn)有的類添加方法
    1. 將一個類的實現(xiàn)拆分成多個獨立的源文件
    1. 聲明私有的方法
2.Category和Class Extension的區(qū)別是什么?
    1. Class Extension是編譯時決議呢岗,在編譯的時候冕香,它的數(shù)據(jù)就已經(jīng)包含在類信息中
    1. Category是運行時決議,在運行時后豫,才會將數(shù)據(jù)合并到類信息中(可通過上面的動畫進行理解^_^
3.Category的實現(xiàn)原理悉尾?
    1. Category編譯之后的底層結構是struct category_t,里面存儲著分類的對象方法挫酿、類方法构眯、屬性、協(xié)議信息
    1. 在程序運行的時候早龟,runtime會將Category的數(shù)據(jù)惫霸,合并到類信息中(類對象、元類對象中)(依舊可通過上面的動畫進行理解-_-||))
4.一個類的有多個分類方法葱弟,分類中都含有與原類同名的方法壹店,請問調用改方法時會調用誰的方法?分類會覆蓋原類的方法嗎芝加?

不會覆蓋硅卢!所有分類的方法會在運行時將它們的方法都合并到一個大數(shù)組中,后面參與編譯的Category數(shù)據(jù)藏杖,會在數(shù)組的前面将塑,然后再將該數(shù)組合并到類信息中,調用時順著方法列表的順序查找蝌麸。

5.load與initialize的區(qū)別

loadinitialize對比章節(jié)的表格

6.Category能否添加成員變量点寥?如果可以,如何給Category添加成員變量来吩?

不能直接給Category添加成員變量开财,但是可以通過關聯(lián)對象或者全局字典等方式間接實現(xiàn)Category有成員變量的效果

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末汉柒,一起剝皮案震驚了整個濱河市误褪,隨后出現(xiàn)的幾起案子责鳍,更是在濱河造成了極大的恐慌,老刑警劉巖兽间,帶你破解...
    沈念sama閱讀 206,311評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件历葛,死亡現(xiàn)場離奇詭異,居然都是意外死亡嘀略,警方通過查閱死者的電腦和手機恤溶,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來帜羊,“玉大人咒程,你說我怎么就攤上這事∷嫌” “怎么了帐姻?”我有些...
    開封第一講書人閱讀 152,671評論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長奶段。 經(jīng)常有香客問我饥瓷,道長,這世上最難降的妖魔是什么痹籍? 我笑而不...
    開封第一講書人閱讀 55,252評論 1 279
  • 正文 為了忘掉前任呢铆,我火速辦了婚禮,結果婚禮上蹲缠,老公的妹妹穿的比我還像新娘棺克。我一直安慰自己,他們只是感情好线定,可當我...
    茶點故事閱讀 64,253評論 5 371
  • 文/花漫 我一把揭開白布娜谊。 她就那樣靜靜地躺著,像睡著了一般渔肩。 火紅的嫁衣襯著肌膚如雪因俐。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,031評論 1 285
  • 那天周偎,我揣著相機與錄音抹剩,去河邊找鬼。 笑死蓉坎,一個胖子當著我的面吹牛澳眷,可吹牛的內容都是我干的。 我是一名探鬼主播蛉艾,決...
    沈念sama閱讀 38,340評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼钳踊,長吁一口氣:“原來是場噩夢啊……” “哼衷敌!你這毒婦竟也來了?” 一聲冷哼從身側響起拓瞪,我...
    開封第一講書人閱讀 36,973評論 0 259
  • 序言:老撾萬榮一對情侶失蹤缴罗,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后祭埂,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體面氓,經(jīng)...
    沈念sama閱讀 43,466評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 35,937評論 2 323
  • 正文 我和宋清朗相戀三年蛆橡,在試婚紗的時候發(fā)現(xiàn)自己被綠了舌界。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,039評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡泰演,死狀恐怖呻拌,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情睦焕,我是刑警寧澤藐握,帶...
    沈念sama閱讀 33,701評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站复亏,受9級特大地震影響趾娃,放射性物質發(fā)生泄漏。R本人自食惡果不足惜缔御,卻給世界環(huán)境...
    茶點故事閱讀 39,254評論 3 307
  • 文/蒙蒙 一抬闷、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧耕突,春花似錦笤成、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至上祈,卻和暖如春培遵,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背登刺。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工籽腕, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人纸俭。 一個月前我還...
    沈念sama閱讀 45,497評論 2 354
  • 正文 我出身青樓皇耗,卻偏偏與公主長得像,于是被迫代替她去往敵國和親揍很。 傳聞我的和親對象是個殘疾皇子郎楼,可洞房花燭夜當晚...
    茶點故事閱讀 42,786評論 2 345

推薦閱讀更多精彩內容