iOS原理篇(三): 關于Category

  • 什么是類別(Category
  • Category的使用場合
  • Category實現原理
  • +load()+initialize()
  • 總結

一、什么是類別

類別(Category)是Objective-C中一個靈活的類擴展機制,用于在不獲悉、不改變原來代碼的情況下往一個已經存在的類中添加新的方法,Category擴展的新方法有更高的優先級,會覆蓋類中同名的已有方法。類別的設計體現了面向對象的核心原則,即開放封閉原則(Open Closed PrincipleOCP)。對擴展開放,對修改封閉,從而降低代碼的耦合度。

二、Category的使用場合

  • 將類的實現分散到多個不同文件或多個不同框架中(為已有的類擴充新的方法
  • 創建對私有方法的前向引用
  • 可以向對象添加非正式協議

類別(Category)和 類擴展(Extension)的區別:

  • 類擴展可以添加屬性,類擴展添加的方法是必須要實現的,類擴展可以認為是一個私有的匿名類別,因為類擴展定義在.m文件頭部,添加的屬性和方法都沒有暴露在頭文件,所以在不考慮運行時特性的前提下,這些擴展屬性和方法只能在類內部使用,一定程度上可以說是實現了私有的機制;
  • 類擴展(Class Extension)在編譯的時候,它的數據就已經包含在類信息中,而類別(Category)是在運行時,才會將數據合并到類信息中;

三、Category實現原理

Category編譯之后的底層結構是struct category_t,里面存儲著分類的對象方法、類方法、屬性、協議信息,在程序運行的時候,runtime會將Category的數據,合并到類信息中(類對象、元類對象中);

下面代碼為DJTPerson添加了兩個分類,之后分析都基于這段代碼

//DJTPerson.h
@interface DJTPerson : NSObject
{
    int _age;
}
- (void)run;
@end

//DJTPerson.m
@implementation DJTPerson
- (void)run
{
    NSLog(@"run");
}
@end
//分類1
//DJTPerson+Test.h
@interface DJTPerson (Test)<NSCoding>
@property (assign, nonatomic) int age;

- (void)test;
+ (void)abc;

-(void)setAge:(int)age;
-(int)age;
@end

//DJTPerson+Test.m
@implementation DJTPerson (Test)
- (void)test
{
    NSLog(@"test");
}

+ (void)abc
{
    NSLog(@"abc");
}
- (void)setAge:(int)age
{
}
- (int)age
{
    return 10;
}
@end
//分類2
//DJTPerson+Test2.h
@interface DJTPerson (Test2)
@end

//DJTPerson+Test2.m"
@implementation DJTPerson (Test2)
- (void)run
{
    NSLog(@"DJTPerson (Test2) - run");
}
@end

我們知道實例對象的 isa指針指向類對象,類對象的isa指針指向元類對象,當person對象調用run方法時,通過isa指針找到類對象,然后在類對象的對象方法列表中查找方法,如果沒找到就通過類對象的superclass指針找到父類對象,接著尋找run方法。
那么調用分類的方法時,是否按照同樣的步驟呢?其實,OC中處理分類需要兩步:

  • 分類經過編譯,會生成_category_t這樣的一個結構體,分類中的數據都會存儲在這個結構體中,多少個分類對應對應多少個這樣的結構體;
  • 編譯完以后,通過runtime動態將生成的結構體中存放的分類數據合并到類對象、元類對象中。

為了驗證以上描述,首先通過clang編譯器將分類轉換為C++,查看分類編譯后結構:
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc DJTPerson+Test.m
在生成的C++文件中,會生成一個_category_t的結構體

struct _category_t {
    const char *name;//所屬類名,本文為DJTPerson
    struct _class_t *cls;
    const struct _method_list_t *instance_methods;// 對象方法
    const struct _method_list_t *class_methods;// 類方法
    const struct _protocol_list_t *protocols;// 協議
    const struct _prop_list_t *properties;// 屬性
};

_category_t結構體中,存放著類名name、對象方法列表instanceMethods、類方法列表classMethods、協議列表protocols和屬性列表classProperties;并沒有存放成員變量的列表,這也說明了分類中是不允許添加成員變量的,分類中添加的屬性也不會幫助我們自動生成成員變量,只會生成getset方法的聲明,需要我們自己去實現;
接著,在文件中我們可以看到_method_list_t類型的結構體:

static struct /*_method_list_t*/ {
    unsigned int entsize;  // sizeof(struct _objc_method)
    unsigned int method_count;
    struct _objc_method method_list[3];
} _OBJC_$_CATEGORY_INSTANCE_METHODS_DJTPerson_$_Test __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    sizeof(_objc_method),
    3,
    {{(struct objc_selector *)"test", "v16@0:8", (void *)_I_DJTPerson_Test_test},
    {(struct objc_selector *)"setAge:", "v20@0:8i16", (void *)_I_DJTPerson_Test_setAge_},
    {(struct objc_selector *)"age", "i16@0:8", (void *)_I_DJTPerson_Test_age}}
};

_OBJC_$_CATEGORY_INSTANCE_METHODS_DJTPerson_$_Test這個結構體名字看出它是 INSTANCE_METHODS對象方法,并且為這個結構體賦值,結構體中存儲了方法占用的內存、方法數量和方法列表。并且找到分類中我們實現的對象方法:testsetAgeage

同樣的,可以看到C++文件中還包含了_method_list_t類型的類方法結構體:

static struct /*_method_list_t*/ {
    unsigned int entsize;  // sizeof(struct _objc_method)
    unsigned int method_count;
    struct _objc_method method_list[1];
} _OBJC_$_CATEGORY_CLASS_METHODS_DJTPerson_$_Test __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    sizeof(_objc_method),
    1,
    {{(struct objc_selector *)"abc", "v16@0:8", (void *)_C_DJTPerson_Test_abc}}
};

從名字也看出_OBJC_$_CATEGORY_CLASS_METHODS_DJTPerson_$_Test這個就是類方法列表結構體;

接下來是協議方法列表:

static struct /*_method_list_t*/ {
    unsigned int entsize;  // sizeof(struct _objc_method)
    unsigned int method_count;
    struct _objc_method method_list[2];
} _OBJC_PROTOCOL_INSTANCE_METHODS_NSCoding __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    sizeof(_objc_method),
    2,
    {{(struct objc_selector *)"encodeWithCoder:", "v24@0:8@16", 0},
    {(struct objc_selector *)"initWithCoder:", "@24@0:8@16", 0}}
};

struct _protocol_t _OBJC_PROTOCOL_NSCoding __attribute__ ((used)) = {
    0,
    "NSCoding",
    0,
    (const struct method_list_t *)&_OBJC_PROTOCOL_INSTANCE_METHODS_NSCoding,
    0,
    0,
    0,
    0,
    sizeof(_protocol_t),
    0,
    (const char **)&_OBJC_PROTOCOL_METHOD_TYPES_NSCoding
};
struct _protocol_t *_OBJC_LABEL_PROTOCOL_$_NSCoding = &_OBJC_PROTOCOL_NSCoding;

static struct /*_protocol_list_t*/ {
    long protocol_count;  // Note, this is 32/64 bit
    struct _protocol_t *super_protocols[1];
} _OBJC_CATEGORY_PROTOCOLS_$_DJTPerson_$_Test __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    1,
    &_OBJC_PROTOCOL_NSCoding
};

屬性列表:

static struct /*_prop_list_t*/ {
    unsigned int entsize;  // sizeof(struct _prop_t)
    unsigned int count_of_properties;
    struct _prop_t prop_list[1];
} _OBJC_$_PROP_LIST_DJTPerson_$_Test __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    sizeof(_prop_t),
    1,
    {{"age","Ti,N"}}
};

最后在_OBJC_$_CATEGORY_DJTPerson_$_Test結構體中對上面的結構體一一賦值:

extern "C" __declspec(dllimport) struct _class_t OBJC_CLASS_$_DJTPerson;
static struct _category_t _OBJC_$_CATEGORY_DJTPerson_$_Test __attribute__ ((used, section ("__DATA,__objc_const"))) = 
{
    "DJTPerson",
    0, // &OBJC_CLASS_$_DJTPerson,
    (const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_DJTPerson_$_Test,
    (const struct _method_list_t *)&_OBJC_$_CATEGORY_CLASS_METHODS_DJTPerson_$_Test,
    (const struct _protocol_list_t *)&_OBJC_CATEGORY_PROTOCOLS_$_DJTPerson_$_Test,
    (const struct _prop_list_t *)&_OBJC_$_PROP_LIST_DJTPerson_$_Test,
};
static void OBJC_CATEGORY_SETUP_$_DJTPerson_$_Test(void ) {
    _OBJC_$_CATEGORY_DJTPerson_$_Test.cls = &OBJC_CLASS_$_DJTPerson;
}

最后將_OBJC_$_CATEGORY_DJTPreson_$_Testcls指針指向_class_t類型的OBJC_CLASS_$_DJTPreson結構體地址,所以cls指向的應該是分類的主類類對象的地址

通過以上分析,分類編譯后確實將我們定義的對象方法,類方法,屬性等都存放在_catagory_t結構體中,接下來分析runtime源碼,查看_catagory_t中存儲的方法、屬性、協議等是如何存儲在類對象中的。

首先來到runtime初始化函數

/***********************************************************************
* _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函數中找到_read_images函數,在_read_images函數中我們找到分類相關代碼:

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

這段代碼用來查找有沒有分類,通過_getObjc2CategoryList函數獲取到分類列表進行遍歷,來獲取其中的方法、協議和屬性等,而最終都調用了remethodizeClass(cls)函數,我們查看remethodizeClass(cls)函數:

/***********************************************************************
* remethodizeClass
* Attach outstanding categories to an existing class.
* Fixes up cls's method list, protocol list, and property list.
* Updates method caches for cls and its subclasses.
* Locking: runtimeLock must be held by the caller
**********************************************************************/
static void remethodizeClass(Class cls)
{
    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);
    }
}

分析上述代碼發現attachCategories函數接收了類對象cls和分類數組cats,如一開始寫的代碼所示,一個類可以有多個分類,之前我們說到一個分類對應一個category_t結構體,那么多個分類則將這些這些category_t保存在category_list中。再看attachCategories函數:

// Attach method lists and properties and protocols from categories to a class.
// Assumes the categories in cats are all loaded and sorted by load order, 
// oldest categories first.
static void 
attachCategories(Class cls, category_list *cats, bool flush_caches)
{
    if (!cats) return;
    if (PrintReplacedMethods) printReplacements(cls, cats);

    bool isMeta = cls->isMetaClass();

    // fixme rearrange to remove these intermediate allocations
    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);
}

從源碼看出,首先根據方法列表、屬性列表和協議列表malloc分配內存,根據多少個分類以及每一塊方法需要多少內存來分配相應的內存地址。遍歷分類方法、屬性以及協議放入對應 mlistproplistsprotolosts數組中。
然后通過類對象的data()方法,拿到類對象的class_rw_t結構體rw,而class_rw_t中存放著類對象的方法、屬性和協議等數據,rw結構體通過類對象的data方法獲取,所以rw里面存放這類對象里面的數據。
接著分別通過rw調用方法列表、屬性列表、協議列表的attachList函數,將所有的分類的方法、屬性、協議列表數組傳進去,我們大致可以猜想到在attachList方法內部將分類和本類相應的方法、屬性,和協議進行了合并,我們來看一下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]));
        }
    }

上述源代碼中,array()->lists數組代表類對象原來的方法列表、屬性列表和協議列表, addedLists傳入所有分類的方法列表、屬性列表和協議列表;attachLists函數中最重要的兩個方法為memmove內存移動和memcpy內存拷貝,memmove能保證以前的數據能完整的挪動到指定位置;
經過memmovememcpy方法之后,分類的方法、屬性和協議列表被放在了類對象中原本存儲的方法、屬性和協議列表前面。
那為什么要將分類方法列表追加到本來的對象方法前面呢,這是為了保證分類方法優先調用,我們知道當分類重寫本類的方法時,會覆蓋本類的方法;其實經過上面的分析我們知道本質上并不是覆蓋,而是優先調用,本類的方法依然在內存中,可以通過打印類的所有方法進行查看,再DJTPerson類中存儲著兩個run方法;

- (void)printMethodNamesOfClass:(Class)cls
{
    unsigned int count;
    // 獲得方法數組
    Method *methodList = class_copyMethodList(cls, &count);
    // 存儲方法名
    NSMutableString *methodNames = [NSMutableString string];
    // 遍歷所有的方法
    for (int i = 0; i < count; i++) {
        // 獲得方法
        Method method = methodList[i];
        // 獲得方法名
        NSString *methodName = NSStringFromSelector(method_getName(method));
        // 拼接方法名
        [methodNames appendString:methodName];
        [methodNames appendString:@", "];
    }
    // 釋放
    free(methodList);
    // 打印方法名
    NSLog(@"%@ - %@", cls, methodNames);
}

- (void)viewDidLoad {
    [super viewDidLoad];    
    DJTPreson *p = [[DJTPreson alloc] init];
    [p run];
    [self printMethodNamesOfClass:[DJTPreson class]];
}

打印結果:

2019-05-04[49082:260754984] DJTPerson (Test2) - run
2019-05-04[49082:260754984] DJTPerson - test, run, run, setAge:, setAge:, age, age,

四、+load()+initialize()

+load方法調用原理

+load方法是在runtime加載類、分類的時候調用,每個類、分類的+load方法,在程序運行過程中只調用一次;他和其他方法的調用不同,不是通過消息機制(isa一層層查找),而是在類、分類加載時直接通過函數地址調用。

下面示例代碼中,定義DJTPerson類和它的兩個分類Test1Test2:

// DJTPerson
@interface DJTPerson : NSObject
@end

@implementation DJTPerson
+ (void)load
{
    NSLog(@"DJTPerson +load");
}
@end

//分類DJTPerson+Test1
@interface DJTPerson (Test1)
@end

@implementation DJTPerson (Test1)
+ (void)load
{
    NSLog(@"DJTPerson(Test1) +load");
}
@end

//分類DJTPerson+Test2
@interface DJTPerson (Test2)
@end

@implementation DJTPerson (Test2)
+ (void)load
{
    NSLog(@"DJTPerson(Test2) +load");
}
@end

直接運行程序,即使沒有在main函數中引用上述頭文件,依然有如下打印:


這說明雖然并沒有使用到這些類,但程序運行這些類會被載進內存,就會調用它們的 +load方法;這和前面所說的在分類中定義同名的run方法會覆蓋原類的run方法不同,明明分類中有定義load方法,覆蓋了原類中的load方法,那為什么DJTPerson中的load方法還會被調用呢?(這里說覆蓋并不準確,而是分類中同名方法經合并會放在方法列表的前面);
通過閱讀運行時源碼,在運行是入口找到load_images方法:

load_images方法內部會調用call_load_methods(void)方法

從源碼看出,先調用類的load方法,在調用分類的load方法,這和編譯順序無關,如下調換編譯順序,依然先調用 DJTPersonload方法:


那類的load方法又是如何調用呢?進入call_class_loads()方法:


這里調用類的load方法是直接通過函數指針進行調用,不會向之前調用run方法那樣去方法列表查找,或者說不是通過消息發送機制的形式。


分類的load方法是如何調用呢?進入call_category_loads(void)方法:


我們發現它也是通過指針指向分類load方法地址,然后直接調用;
總結:從上面看出確實是優先調用類的load方法,然后調用分類的load方法;

接著,考慮有多個類,一個類有多個分類,以及類存在父類的情況,我們看看此時+load方法的調用順序,新增DJTDogDJTCat兩個類繼承自NSObject,新增DJTStudent類繼承自DJTPerson,同時為DJTStudent添加兩個分類Test1Test2,編譯順序如下:


從打印結果看出:

  • 一定是優先調用類的+load方法,然后調用分類+load方法,這個和編譯順序無關;
  • 類與類之間+load方法調用順序和編譯順序有關,比如DJTDogDJTCatDJTStudent之前調用+load方法;
  • 在調用子類的+load方法之前,會保證其父類的+load方法已經調用,即父類的+load方法優先調用,即使父類編譯順序在子類后面,比如DJTPersonDJTStudent
  • 分類的+load方法調用順序和編譯順序有關,先編譯先調用。

+initialize方法調用原理

+initialize方法會在類第一次接收到消息時調用,并且它的調用順序是先調用父類的+initialize,再調用子類的+initialize,前提是父類的+initialize方法沒有被調用過。

查看源碼,在objc-class.mm文件中,有一個獲取對象方法的函數class_getInstanceMethod()和一個獲取類方法的函數class_getClassMethod(),在class_getClassMethod()函數中其實也是調用的class_getInstanceMethod()方法,上源碼:

Method class_getInstanceMethod(Class cls, SEL sel)
{
    if (!cls  ||  !sel) return nil;
#warning fixme build and search caches
    // Search method lists, try method resolver, etc.
    lookUpImpOrNil(cls, sel, nil, 
                   NO/*initialize*/, NO/*cache*/, YES/*resolver*/);

#warning fixme build and search caches

    return _class_getMethod(cls, sel);
}

內部調用lookUpImpOrNil(),再調用lookUpImpOrForward():

IMP lookUpImpOrForward(Class cls, SEL sel, id inst, 
                       bool initialize, bool cache, bool resolver)
{
    ...............

    if (initialize  &&  !cls->isInitialized()) {
        runtimeLock.unlockRead();
        _class_initialize (_class_getNonMetaClass(cls, inst));
        runtimeLock.read();
        // 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
    }
    ...................

上面if中判斷傳入的參數initialize,表示是否需要初始化,cls->isInitialized()判斷這個類是否已經初始化,在滿足需要初始化并且類未初始化時,調用_class_initialize()方法進行初始化,我們看一下_class_initialize()中都做了什么:

void _class_initialize(Class cls)
{
    assert(!cls->isMetaClass());
    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);
    }
    // Try to atomically set CLS_INITIALIZING.
    {
        monitor_locker_t lock(classInitLock);
        if (!cls->isInitialized() && !cls->isInitializing()) {
            cls->setInitializing();
            reallyInitialize = YES;
        }
    }
    if (reallyInitialize) {
     .............
     @try
        {
            callInitialize(cls);
            if (PrintInitializing) {
                _objc_inform("INITIALIZE: thread %p: finished +[%s initialize]",
                             pthread_self(), cls->nameForLogging());
            }
        }
    else if (cls->isInitialized()) {
        // Set CLS_INITIALIZING failed because someone else already 
        return;
    }
    else {
        // We shouldn't be here. 
        _objc_fatal("thread-safe class init in objc runtime is buggy!");
    }
}

if判斷中,如果存在父類,并且父類沒有初始化,先去初始化父類,即調用callInitialize(cls)方法:

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

從上面源碼看出,+initialize()方法最終是通過objc_msgSend消息發送機制調用的,消息發送機制通過isa指針找到對應的方法與實現,因此如果分類方法中有實現,會優先調用分類方法中的實現;

綜上,我們為DJTPresonDJTStudentDJTStudent+Test 添加initialize方法,當類第一次接收到消息時(或者說第一次使用類的時),就會調用initialize,在調用子類的initialize之前,會先保證調用父類的initialize方法,如果之前已經調用過initialize,就不會再調用initialize方法了,當分類重寫initialize方法時會先調用分類的方法。

五、總結

  • Catagory中有load方法嗎?load方法是什么時候調用的?load方法能繼承嗎?
    Category中有load方法,load方法在程序啟動裝載類信息的時候就會調用,load方法可以繼承,但一半不會手動去調用,調用子類的load方法之前,會先調用父類的load方法。

  • loadinitialize方法的區別是什么?
    調用方式load是根據函數地址直接調用,initialize是通過objc_msgSend調用;
    調用時刻loadruntime加載類、分類的時候調用(只會調用1次),initialize是類第一次接收到消息的時候調用,每一個類只會initialize一次(父類的initialize方法可能會被調用多次);

  • 它們在Category中調用的順序是什么?以及出現繼承時它們之間的調用過程?
    load方法:先編譯哪個類,就先調用那個類的load,在調用load之前會先調用父類的load方法;分類中load方法不會覆蓋本類的load方法,先編譯的分類優先調用load方法;
    initialize方法:先初始化父類,之后再初始化子類;如果子類沒有實現+initialize,會調用父類的+initialize(所以父類的+initialize可能會被調用多次),如果分類實現了+initialize,就覆蓋類本身的+initialize調用;

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 1.內存管理: 影響:如果app占用內存過大, 系統可能會強制關閉app, 造成閃退現象, 影響用戶體驗. 概念:...
    SoftKnife閱讀 397評論 0 1
  • 這本書的作者是史蒂芬·柯維,相信很多人都知道他的另一本書《高效能人士的七個習慣》,這本書是他去世前寫的最后一...
    樂樂_Mika閱讀 9,427評論 2 6
  • 豆瓣 “一刻” 1.體驗背景 機型版本 產品名稱 時間 M355 Android4.4.4 一刻1.6.0...
    綺麗的夏天閱讀 268評論 0 1