談?wù)刬nitialize的實質(zhì)和真正調(diào)用方式

iOS中類的+load和initialize是兩個比較特殊的方法。+load的方法調(diào)用比較早,在dyld啟動過程中發(fā)出dyld_image_state_dependents_initialized的通知,objc-runtime 會執(zhí)行load_images這個方法,里面會按照一定的規(guī)律加載所有類的+load方法,并執(zhí)行。當(dāng)然這不是這一節(jié)的重點,我們主要將initialize。

下面是+initialize在objc中的調(diào)用:

/***********************************************************************
* class_initialize.  Send the '+initialize' message on demand to any
* uninitialized class. Force initialization of superclasses first.
**********************************************************************/
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) {
        // We successfully set the CLS_INITIALIZING bit. Initialize the class.
        
        // Record that we're initializing this class so we can message it.
        _setThisThreadIsInitializingClass(cls);
        
        // Send the +initialize message.
        // Note that +initialize is sent to the superclass (again) if 
        // this class doesn't implement +initialize. 2157218
        if (PrintInitializing) {
            _objc_inform("INITIALIZE: calling +[%s initialize]",
                         cls->nameForLogging());
        }

        // Exceptions: A +initialize call that throws an exception 
        // is deemed to be a complete and successful +initialize.
        @try {
            callInitialize(cls);

            if (PrintInitializing) {
                _objc_inform("INITIALIZE: finished +[%s initialize]",
                             cls->nameForLogging());
            }
        }
        @catch (...) {
            if (PrintInitializing) {
                _objc_inform("INITIALIZE: +[%s initialize] threw an exception",
                             cls->nameForLogging());
            }
            @throw;
        }
        @finally {
            // Done initializing. 
            // If the superclass is also done initializing, then update 
            //   the info bits and notify waiting threads.
            // If not, update them later. (This can happen if this +initialize 
            //   was itself triggered from inside a superclass +initialize.)
            monitor_locker_t lock(classInitLock);
            if (!supercls  ||  supercls->isInitialized()) {
                _finishInitializing(cls, supercls);
            } else {
                _finishInitializingAfter(cls, supercls);
            }
        }
        return;
    }
    
    else if (cls->isInitializing()) {
        // We couldn't set INITIALIZING because INITIALIZING was already set.
        // If this thread set it earlier, continue normally.
        // If some other thread set it, block until initialize is done.
        // It's ok if INITIALIZING changes to INITIALIZED while we're here, 
        //   because we safely check for INITIALIZED inside the lock 
        //   before blocking.
        if (_thisThreadIsInitializingClass(cls)) {
            return;
        } else {
            waitForInitializeToComplete(cls);
            return;
        }
    }
    
    else if (cls->isInitialized()) {
        // Set CLS_INITIALIZING failed because someone else already 
        //   initialized the class. Continue normally.
        // NOTE this check must come AFTER the ISINITIALIZING case.
        // Otherwise: Another thread is initializing this class. ISINITIALIZED 
        //   is false. Skip this clause. Then the other thread finishes 
        //   initialization and sets INITIALIZING=no and INITIALIZED=yes. 
        //   Skip the ISINITIALIZING clause. Die horribly.
        return;
    }
    
    else {
        // We shouldn't be here. 
        _objc_fatal("thread-safe class init in objc runtime is buggy!");
    }
}

initialize會在類收到第一條消息時調(diào)用。在

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

這段代碼中,發(fā)現(xiàn)要實現(xiàn)某個類的initialize方法,一定要在父類initialize之后,若父類尚未執(zhí)行過initialize這個方法,會在繼承鏈中遞歸調(diào)用initialize,直到某個父類曾被發(fā)送所消息,觸發(fā)終止遞歸的條件。而在下面的執(zhí)行中會調(diào)用到 callInitialize(cls);這行代碼,我們看看callInitialize的實現(xiàn):

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

實質(zhì)是調(diào)用了objc_msgSend這個方法,走的是消息轉(zhuǎn)發(fā)機制。而消息轉(zhuǎn)發(fā)機制調(diào)用方法的基本機制,就是類有分類則執(zhí)行分類方法,無分類走主類,主類中沒有則到父類中尋找。到現(xiàn)在我們已經(jīng)清楚了initialize的實現(xiàn)機制,下面看幾個例子:

// parent
#import <Foundation/Foundation.h>
@interface Parent : NSObject
@end
#import "Parent.h"
#import "Child.h"
@implementation Parent

+(void)initialize {
    NSLog(@"parent");
}

@end

// child
#import "Parent.h"
@interface Child : Parent
@end
#import "Child.h"
@implementation Child

+(void)initialize {
    NSLog(@"child");
}
@end

// ChildTwo
#import "Parent.h"
@interface ChildTwo : Parent
@end
#import "ChildTwo.h"

@implementation ChildTwo
+(void)initialize {
    NSLog(@"ChildTwo");
}
@end


#import "ViewController.h"
#import "Parent.h"
#import "Child.h"
#import "ChildTwo.h"

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    Child *child = [[Child alloc]init];
    
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3* NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        ChildTwo *childTwo = [[ChildTwo alloc]init];
    });
    
}

@end

實現(xiàn)3個類:Parent、 Child、 ChildTwo, Child和ChildTwo 繼承自Parent,父類子類全部實現(xiàn)了initialize方法。我們看看執(zhí)行結(jié)果:

2018-11-02 11:07:49.817869+0800 Initialize[47370:4909614] parent
2018-11-02 11:07:49.817998+0800 Initialize[47370:4909614] child
2018-11-02 11:07:52.818174+0800 Initialize[47370:4909614] ChildTwo

像Child發(fā)現(xiàn)消息時,Parent尚未被發(fā)送過消息,
回先執(zhí)行Parent的initialize方法,然后執(zhí)行Child的initialize方法,隨后在幾秒后向ChildTwo發(fā)送消息,此時Parent已經(jīng)initialize,Parent類的initialize標(biāo)志位已經(jīng)為真,不會再執(zhí)行initialize方法,直接執(zhí)行自己的initialize方法。
我們?nèi)サ鬋hildTwo的initialize實現(xiàn)再看看:

#import "Parent.h"
@interface ChildTwo : Parent
@end
#import "ChildTwo.h"

@implementation ChildTwo

@end

執(zhí)行結(jié)果為

2018-11-02 11:14:26.653112+0800 Initialize[47479:4915495] parent
2018-11-02 11:14:26.653230+0800 Initialize[47479:4915495] child
2018-11-02 11:14:29.653940+0800 Initialize[47479:4915495] parent

因為ChildTwo沒有實現(xiàn)initialize方法,所以在收到消息時會去繼承鏈中查找initialize實現(xiàn),找到為止,所以打印的父類的initialize實現(xiàn)。由此可知,父類initialize方法在某種情況下是會實現(xiàn)多次的。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • load,再啟動之前調(diào)用 1:父類 2:子類 3:分類 根據(jù)函數(shù)地址調(diào)用 initialize,在初次使用類對象的...
    哈哈哈我的簡書賬號閱讀 464評論 1 1
  • load load方法在這個文件被程序裝載時調(diào)用(這個類被加入內(nèi)存的時候調(diào)用),這與這個類是否被用到無關(guān),因此lo...
    攝影師諾風(fēng)閱讀 521評論 0 0
  • Objective-C作為一門面向?qū)ο笳Z言,有類和對象的概念。所有的代碼在執(zhí)行編譯后,類相關(guān)的數(shù)據(jù)結(jié)構(gòu)會保留在目標(biāo)...
    大雄記閱讀 1,639評論 0 5
  • 看到這個,很容易想到“迪斯科”,這一個“古老”的名詞,知道它的人容易暴露年齡哈,這是在80年代較為流行的跳舞音樂名...
    曹勇企業(yè)培訓(xùn)師閱讀 956評論 1 5
  • 從一個事故說起 上周某個工作日的晚上,我們突然發(fā)現(xiàn)平臺的后臺監(jiān)控系統(tǒng)報警連連,故障點指向我們的某家CDN供...
    藍(lán)色眼鏡007閱讀 779評論 1 0

友情鏈接更多精彩內(nèi)容