日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

+initialize方法的调用时机

發布時間:2023/12/18 编程问答 33 豆豆
生活随笔 收集整理的這篇文章主要介紹了 +initialize方法的调用时机 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

+initialize方法的調用時機

一個類或者它的子類收到第一條消息(手寫代碼調用,+load方法不算)之前調用,可以做一些初始化的工作。但該類的+initialize的方法調用,在其父類之后。

Runtime運行時以線程安全的方式將+initialize消息發送給類。也就是說,當一個類首次要執行手動調用的代碼之前,會等待+initialize方法執行完畢后,再調用該方法。

這里需要注意的一點:

當子類沒有實現+initialize或者子類在+initialize中顯式的調用了[super initialize],那么父類的+initialize方法會被調用多次。如果希望避免某一個類中的+initialize方法被調用過多次,可以使用下面的方法來實現:

+ (void)initialize {if (self == [ClassName self]) {// ... do the initialization ...} }

因為+initialize是以阻塞方式調用的,所以很重要的一點就是將方法實現限制為可能最小的工作量。

Demo演示

接下來我們寫一個demo來驗證一下,首先創建一個Person類,和Person的兩個分類Test1,Test2。

@interface Person : NSObject@end@implementation Person+ (void)initialize {NSLog(@"---- %p %s", self, __FUNCTION__); }@end@interface Person (Test1)@end@implementation Person (Test1)+ (void)initialize {NSLog(@"---- %p %s", self, __FUNCTION__); }@end@interface Person (Test2)@end@implementation Person (Test2)+ (void)initialize {NSLog(@"---- %p %s", self, __FUNCTION__); }@end

然后新建一個Student類繼承Person類,并實現Student的兩個分類Test1,Test2。

@interface Student : Person@end@implementation Student+ (void)initialize {NSLog(@"---- %p %s", self, __FUNCTION__); }@end@interface Student (Test1)@end@implementation Student (Test1)+ (void)initialize {NSLog(@"---- %p %s", self, __FUNCTION__); }@end@interface Student (Test2)@end@implementation Student (Test2)+ (void)initialize {NSLog(@"---- %p %s", self, __FUNCTION__); }@end

然后我們什么都不調用,直接運行程序,那應該也不會打印任何信息。

然后,我們調用[Person alloc]方法,再次運行,查看結果。

---- 0x10f53ef60 +[Person(Test2) initialize]

這里的輸出分類的+initialize方法,查看一下Compile Sources,發現Person(Test2)分類在最后編譯,所以調用的是它實現的+initialize方法。

這里,我們修改下代碼,調用[Student alloc],然后看下運行結果。

---- 0x10c08af60 +[Person(Test2) initialize] ---- 0x10c08afb0 +[Student(Test1) initialize]

這里發現,Student調用+initialize方法時是會先調用父類的+initialize方法(如果代碼中沒有寫父類的調用代碼)。

然后我們繼續新建一個HighSchoolStudent繼承Student,什么都不實現,同時調用[HighSchoolStudent alloc],查看結果。

---- 0x101070048 +[Person(Test2) initialize] ---- 0x101070098 +[Student(Test1) initialize] ---- 0x10106fff8 +[Student(Test1) initialize]

確實這樣會調用兩次+[Student(Test1) initialize]方法。

[HighSchoolStudent initialize]的調用,是該類對象通過isa指針找到元類對象,在元類已緩存的方法列表中查方法,如果有就調用;如果沒有就查找方法列表,如果還沒有找到,那么就去類的父類的元類對象中查找,找到就調用。如果沒有就遞歸下去直到NSObject的元類對象。所以找到了Student的元類對象,執行了initialize方法。

源碼分析

我們可以在Runtime的源碼中,分析一下調用+initialize方法的實現。首先一個類要調用方法時肯定是調用類方法,那么就先從class_getInstanceMethod方法入手查看吧。

/*********************************************************************** * class_getInstanceMethod. Return the instance method for the * specified class and selector. **********************************************************************/ Method class_getInstanceMethod(Class cls, SEL sel) {if (!cls || !sel) return nil;// This deliberately avoids +initialize because it historically did so.// This implementation is a bit weird because it's the only place that // wants a Method instead of an IMP.#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 cachesreturn _class_getMethod(cls, sel); }

這里可以看到基本上就是調用了lookUpImpOrNil函數,而且函數列表中有注釋標記了第四個參數/*initalize*/。那我們繼續看。

/*********************************************************************** * lookUpImpOrNil. * Like lookUpImpOrForward, but returns nil instead of _objc_msgForward_impcache **********************************************************************/ IMP lookUpImpOrNil(Class cls, SEL sel, id inst, bool initialize, bool cache, bool resolver) {IMP imp = lookUpImpOrForward(cls, sel, inst, initialize, cache, resolver);if (imp == _objc_msgForward_impcache) return nil;else return imp; }

然后進入lookUpImpOrForward函數中繼續追蹤,該方法有些長,只截取了有關的部分代碼。

/*********************************************************************** * lookUpImpOrForward. * The standard IMP lookup. * initialize==NO tries to avoid +initialize (but sometimes fails) * cache==NO skips optimistic unlocked lookup (but uses cache elsewhere) * Most callers should use initialize==YES and cache==YES. * inst is an instance of cls or a subclass thereof, or nil if none is known. * If cls is an un-initialized metaclass then a non-nil inst is faster. * May return _objc_msgForward_impcache. IMPs destined for external use * must be converted to _objc_msgForward or _objc_msgForward_stret. * If you don't want forwarding at all, use lookUpImpOrNil() instead. **********************************************************************/ IMP lookUpImpOrForward(Class cls, SEL sel, id inst, bool initialize, bool cache, bool resolver) {// other code ..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}// other code .. }

這里判斷如果需要初始化并且該類沒有初始化,那么就進行類初始化。我們發現,我們從入口到這里找的話,其實傳遞進來的initialize其實是NO,不會進入初始化,但是咱們找到的地方是對的,我們繼續來看代碼,了解完實現之后,我們繼續看哪里調用該函數時傳遞的initialize的值是YES。接下來我們進入_class_initialize函數,代碼依舊很長,只截取了相關的部分代碼。

/*********************************************************************** * 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) {// other code .. #if __OBJC2__@try #endif{callInitialize(cls);if (PrintInitializing) {_objc_inform("INITIALIZE: thread %p: finished +[%s initialize]",pthread_self(), cls->nameForLogging());}} #if __OBJC2__@catch (...) {if (PrintInitializing) {_objc_inform("INITIALIZE: thread %p: +[%s initialize] ""threw an exception",pthread_self(), cls->nameForLogging());}@throw;}@finally #endif{// Done initializing.lockAndFinishInitializing(cls, supercls);}return;}// other code .. }

在這里我們看到,首先如果父類存在并且父類沒有初始化過,那么調用_class_initialize函數來初始化父類,直到整條集成鏈上的所有類都初始化完畢。之后調用callInitialize函數來初始化自己。

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

這里很熟悉了,通過消息機制調用initialize方法。到這里能解釋了,為什么initialize會調用父類的initialize方法。

回來我們繼續說一下什么時候調用lookUpImpOrForward是傳遞的initialize的值為YES

  • _class_lookupMethodAndLoadCache3方法,最終調用是匯編層調用,也就是說我們的代碼調用,代碼調用前是需要這個類初始化完畢的。這里就是我們要找的入口
  • objc_loadWeakRetained、weak_register_no_lock因為這兩個方法會調用SEL_retainWeakReference這個selector,所以要判斷是否已經初始化完畢。
  • methodForSelector、methodFor、instanceMethodFor這三個方法也是獲取IMP的,所以也是需要這個類初始化完畢,否則通過Runtime增加的一些方法,可能無法獲取到。
  • 總結

    本文主要通過官方文檔、例子以及Runtime源碼,分析了+initialize方法的調用,總結如下:

  • 當代碼執行到一個類第一次調用方法時,會調用這個類的+initialize方法
  • 在調用自身類的+initialize方法之前,會判斷其父類鏈上是否有類還沒有執行+initialize方法,如果沒有執行,那么執行。所以所有父類的+initialize方法都執行在前,子類的+initialize執行在后。
  • 如果一個類有多個分類都實現了+initialize方法,那么會執行編譯順序的最后一個分類實現的+initialize方法
  • 當一個類實現了+initialize方法,但是子類沒有實現+initialize或者子類在實現+initialize方法中顯式的調用的[super initialize]方法,那么該類的+initialize方法會調用多次,如果不想該方法被多次調用,可以在該類的+initialize方法通過if (self = [ClassName self])進行判斷來避免多次調用。
  • +load方法與+initialize方法的區別


    調用方式

    +load方法

    根據函數地址直接調用

    +initialize方法

    是通過objc_msgSend調用


    調用時機

    +load方法

    是Runtime加載類、分類的時候調用(如果不顯式調用,只會調用一次)

    +initialize方法

    是類第一次接收到消息的時候調用(如果不顯式調用,可能存在調用多次的風險)


    調用順序

    +load方法
    • 先調用類的+load方法,再調用分類的+load方法
    • 有繼承關系的類,先調用父類的+load,后調用子類的+load方法
    • 沒有繼承關系的類,會按照編譯順序來執行+load方法
    • 所有的分類,都按照編譯順序來執行+load方法
    +initialize方法
    • 先調用父類的+initialize方法,后調用子類的+initialize方法
    • 如果一個類有分類,那么會調用最后編譯的分類實現的+initialize方法
    • 通過消息機制調用,當子類沒有實現+initialize方法時,會調用父類的initialize方法

    總結

    以上是生活随笔為你收集整理的+initialize方法的调用时机的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。