+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
總結
本文主要通過官方文檔、例子以及Runtime源碼,分析了+initialize方法的調用,總結如下:
+load方法與+initialize方法的區別
調用方式
+load方法
根據函數地址直接調用
+initialize方法
是通過objc_msgSend調用
調用時機
+load方法
是Runtime加載類、分類的時候調用(如果不顯式調用,只會調用一次)
+initialize方法
是類第一次接收到消息的時候調用(如果不顯式調用,可能存在調用多次的風險)
調用順序
+load方法
- 先調用類的+load方法,再調用分類的+load方法
- 有繼承關系的類,先調用父類的+load,后調用子類的+load方法
- 沒有繼承關系的類,會按照編譯順序來執行+load方法
- 所有的分類,都按照編譯順序來執行+load方法
+initialize方法
- 先調用父類的+initialize方法,后調用子類的+initialize方法
- 如果一個類有分類,那么會調用最后編譯的分類實現的+initialize方法
- 通過消息機制調用,當子類沒有實現+initialize方法時,會調用父類的initialize方法
總結
以上是生活随笔為你收集整理的+initialize方法的调用时机的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 搜索引擎优化有哪些方法?分享SEO搜索引
- 下一篇: 电脑重复文件扫描工具清理:Easy du