Objective-C(十一)load和initialize

本文是Objective-C系列的第11篇,主要講述了及loadinitialize兩個特殊方法的相關特性及其底層的實現。git

1、經常使用知識點

在講述以前,咱們先把該兩個方法經常使用到的一些知識點先列出。ui

image-20181211200612904

2、+load

2.1 源碼導讀

根據下列順序,閱讀objc源碼便可。spa

image-20181201135843918

2.2 測試用例

測試用例源碼在**02-load-initialize**ssr

2.2.1 類與父類含+load

  • 先調用父類的+load方法
  • 再調用類的+load方法
@implementation BFPerson
+ (void)load
{
    NSLog(@"+[BFPerson load]");
}
@end

@implementation BFBoy
+ (void)load
{
    NSLog(@"+[BFBoy load]");
}
@end
複製代碼

輸出日誌:

14:21:44.542004+0800 Category[4483:5050573] +[BFPerson load]

14:21:44.542545+0800 Category[4483:5050573] +[BFBoy load]

2.2.2 分類有load方法

  • 先調用類的+load方法
  • 再調用分類的+load方法
    • 分類的+load方法調用順序與編譯順序一致
@implementation BFBoy
+ (void)load
{    NSLog(@"+[BFBoy load]");}
@end

@implementation BFBoy (Handsome)
+ (void)load
{  NSLog(@"+[BFBoy load]--Handsome Cat");}
@end


@implementation BFBoy (Tall)
+ (void)load
{	NSLog(@"+[BFBoy load]--Tall Cat"); }
@end
複製代碼

輸出日誌:

14:24:09.679907+0800 Category[4565:5054709] +[BFPerson load]

14:24:09.680555+0800 Category[4565:5054709] +[BFBoy load]

14:24:09.680632+0800 Category[4565:5054709] +[BFPerson load]--Wrok Cat

14:24:09.680749+0800 Category[4565:5054709] +[BFBoy load]--Tall Cat

14:24:09.680845+0800 Category[4565:5054709] +[BFBoy load]--Handsome Cat

2.3 核心流程

2.3.1 抽取+load方法

  • (1)抽取類的+load方法
    • a.先抽取父類的+load方法
    • b.再抽取子類的+load方法
  • (2)抽取分類的+load方法
void prepare_load_methods(const headerType *mhdr)
{
    //1.獲取非懶加載的類列表
    classref_t *classlist = _getObjc2NonlazyClassList(mhdr, &count);
    for (i = 0; i < count; i++) {
        //1. 先將類及其父類的load ---> loadable_classes
        schedule_class_load(remapClass(classlist[i]));
    }

    //2.獲取分類列表
    category_t **categorylist = _getObjc2NonlazyCategoryList(mhdr, &count);
    for (i = 0; i < count; i++) {
        // 2. 將分類的load ---> loadable_categories
        add_category_to_loadable_list(cat);
    }
}
複製代碼

2.3.2 調用+load方法

調用load方法,即將上一步抽取出來的方法列表loadable_classesloadable_categories,逐一調用便可。

  • (1)先調用類(包含父類)的+load方法
    • a.先調用父類+load方法
    • b.再調用子類+load方法
  • (2)調用分類的+load方法
void call_load_methods(void)
{
    do {
        // 1. 會先迭代調用完全部的類的load方法
        while (loadable_classes_used > 0) {
            call_class_loads();
        }
        // 2. 依次調用全部分類的load方法
        more_categories = call_category_loads();
    } while (loadable_classes_used > 0  ||  more_categories);
}
複製代碼

2.4. 源碼截取

2.4.1 抽取類+load 方法

void add_class_to_loadable_list(Class cls)
{
    IMP method;

    loadMethodLock.assertLocked();

    method = cls->getLoadMethod();  //獲取類中load的IMP
    if (!method) return;  // Don't bother if cls has no +load method
    
    if (PrintLoading) {
        _objc_inform("LOAD: class '%s' scheduled for +load", 
                     cls->nameForLogging());
    }
    
    /* 1. loadable_class :Class load方法的結構體 1.1 class與load一一對應 1.2 struct loadable_class { Class cls; // may be nil IMP method; }; 2. loadable_classes 存放loadable_class列表 3. loadable_classes_used 當前load存放在loadable_class序號 4. loadable_classes 每次分配的內存爲 loadable_classes_allocated*2 + 16; 4.1 realloc void *realloc(void *ptr, size_t size) 從新調整以前調用 malloc 或 calloc 所分配的 ptr 所指向的內存塊的大小。 */

    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_used].cls = cls;
    loadable_classes[loadable_classes_used].method = method;
    loadable_classes_used++;
}
複製代碼

2.4.2 分類方法抽取

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));
    }
    /* 1. loadable_category :存放Category load方法的結構體 1.1 class與load一一對應 1.2 struct loadable_category { Category cat; // may be nil IMP method; }; 2. loadable_categories 存放loadable_category列表 3. loadable_categories_used 當前load存放在loadable_category序號 4. loadable_categories 分配的內存 4.1 realloc void *realloc(void *ptr, size_t size) 從新調整以前調用 malloc 或 calloc 所分配的 ptr 所指向的內存塊的大小。 4.2 每次分配爲 loadable_categories_allocated*2 + 16; 即上次分配後,一直能使用到(上次的2倍+16)次,纔會再次分配 */
    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_used].cat = cat;
    loadable_categories[loadable_categories_used].method = method;
    loadable_categories_used++;
}
複製代碼

2.4.3 +load方法調用

(*load_method)(cls, SEL_load);
複製代碼

3、+initialize

3.1 源碼導讀

因爲+initialize是在類第一次接收到消息是調用,咱們調用**[BFPerson alloc];**時,就會調用。

咱們根據彙編debug獲得了下面流程:

> 1. objc_msgSend
> 2. objc_msgSend_uncached
> 3._class_lookupMethodAndLoadCache3
> 4. lookUpImpOrForward
> 5. _class_initialize
	5.1>> _setThisThreadIsInitializingClass(objc_class*)
		5.1.1>>> _fetchInitializingClassList
    5.2>> CALLING_SOME+initialize_METHOD:
		5.2.1>>> objc_msgSend(cls, SEL_initialize) 
		//這一步會打印 +[BFPerson initialize]--Study Cat
	5.3>> lockAndFinishInitializing
複製代碼

而後,咱們去objc4源碼挖礦。

咱們發現,12在源碼中都是彙編,3是可讀的C代碼。就從3開始。

image-20181201074217670

3.2 測試用例

測試用例源碼在**02-load-initialize**

3.2.1 子類重寫**+initialize**

  • 優先調用父類initialize
@implementation BFPerson
+ (void)initialize
{
    NSLog(@"+[BFPerson initialize]");
}
@end

@implementation BFBoy
+ (void)initialize
{
    NSLog(@"+[BFBoy initialize]");
}
@end
複製代碼

調用

[BFBoy alloc];
複製代碼

此時打印以下:

13:44:35.437131+0800 Category[3527:4997267] +[BFPerson initialize]

13:44:35.437240+0800 Category[3527:4997267] +[BFBoy initialize]

3.2.2 分類重寫**+initialize**

  • 會調用分類+initialize,不調用類自己的+initialize
@implementation BFPerson
+ (void)initialize
{
    NSLog(@"+[BFPerson initialize]");
}
@end

@implementation BFPerson (Work)
+ (void)initialize
{
    NSLog(@"+[BFBoy initialize]");
}
@end
複製代碼

調用:

[BFBoy alloc];
複製代碼

打印輸出

13:45:35.437121+0800 Category[3527:4997267] +[BFPerson initialize]--Wrok Cat

3.2.3 屢次調用initialize

多個子類均未實現,但父類實現+initialize方法,會屢次調用+initialize

@implementation BFPerson
+ (void)initialize
{
    NSLog(@"+[BFPerson initialize]");
}
@end

@implementation BFBoy
@end

@implementation BFGirl
@end
複製代碼

調用:

[BFBoy alloc];
[BFGirl alloc];
複製代碼

打印輸出

13:52:44.968194+0800 Category[3786:5010745] +[BFPerson initialize]

13:52:44.968327+0800 Category[3786:5010745] +[BFPerson initialize]

13:52:44.968427+0800 Category[3786:5010745] +[BFPerson initialize]

3.3. 核心流程

類的初始化加載,會先調用父類+initialize方法,再調用類自己的+initialize

//僞代碼
BOOL personInitialized = NO;
BOOL boyInitialized = NO;

if (!boyInitialized) {
    //1. 父類調用
    if (!personInitialized) {
        objc_msgSend([BFPerson class], @selector(initialize));
        personInitialized = YES;
    }
    //2. 類自己調用
	objc_msgSend([BFBoy class], @selector(initialize));
	boyInitialized = YES;
}
複製代碼

3.4. 源碼截取

3.4.1 尋找方法

IMP lookUpImpOrForward(Class cls, SEL sel, id inst, 
                       bool initialize, bool cache, bool resolver)
{
    ....
	//須要初始化,且類未進行初始化
    if (initialize  &&  !cls->isInitialized()) {
        //initialize
        _class_initialize (_class_getNonMetaClass(cls, inst));
    }
    ....
}
複製代碼

3.4.2 類的+initialize調用

void _class_initialize(Class cls)
{
    // 1. 先調用父類 initialization
    if (supercls  &&  !supercls->isInitialized()) {
        _class_initialize(supercls);
    }
    ....
    //2. 給類發送initialize消息
    callInitialize(cls);
    ....
    //3. 完成類的初始化
    lockAndFinishInitializing(cls, supercls);
    ...
}
複製代碼

參考

連接

  1. objc源碼

示例代碼

  1. 02-load-initialize
相關文章
相關標籤/搜索