OC load 和 initialize 方法
OC中有兩個特殊的類方法,分別是load和initialize。
先來看看NSObject Class Reference里對這兩個方法說明:
+(void)load
The?load?message is sent to classes and categories that are both dynamically loaded and statically linked, but only if the newly loaded class or category implements a method that can respond.// 只要類或類別實現了load方法, 不論這個類或類別是動態加載還是靜態鏈接, 類/類別都會受到load消息。
The order of initialization is as follows:
In addition:
- A class’s?+load?method is called after all of its superclasses’?+load?methods.
- A category?+load?method is called after the class’s own?+load?method.
- 先父類load-> 在子類load->最后category中load(父類的category優先級也低于子類)
In a custom implementation of?load?you can therefore safely message other unrelated classes from the same image, but any?load?methods implemented by those classes may not have run yet. //?在自定義的實現+load方法中,可以安全地向同一二進制包中的其它無關的類發送消息,但接收消息的類中的+load方法可能尚未被調用。
?
+(void)initialize
The runtime sends?initialize?to each class in a program exactly one time just before the class, or any class that inherits from it, is sent its first message from within the program. (Thus the method may never be invoked if the class is not used.) The runtime sends the initialize message to classes in a thread-safe manner.?Superclasses receive this message before their subclasses. // 在這個類接受第一個消息之前,?runtime人會給每一個類發送initialize消息, 先父類在子類, initialize會保證線程安全
Apple的文檔很清楚地說明了initialize和load的區別在于:load是只要類所在文件被引用就會被調用,而initialize是在類或者其子類的第一個方法被調用前調用。所以如果類沒有被引用進項目,就不會有load調用;但即使類文件被引用進來,但是沒有使用,那么initialize也不會被調用。
load
顧名思義,load方法在這個文件被程序裝載時調用。只要是在Compile Sources中出現的文件總是會被裝載,這與這個類是否被用到無關,load方法總是在main函數之前調用。
調用規則
如果一個類實現了load方法,在調用這個方法前會首先調用父類的load方法。而且這個過程是自動完成的,并不需要我們手動實現,由于load函數是系統自動加載的,因此不需要再調用[super load],否則父類的load函數會多次執行。:
// In Parent.m + (void)load {NSLog(@"Load Class Parent"); }// In Parent+load.m + (void)load {NSLog(@"Load Class Parent+load"); }// In Child.m,繼承自Parent + (void)load {NSLog(@"Load Class Child"); }// In Child+load.m,Child類的分類 + (void)load {NSLog(@"Load Class Child+load"); }// 運行結果: Parent+load和Child+load的順序和文件編譯順序有關 /*2016-02-01 21:28:14.379 load[11789:1435378] Load Class Parent2016-02-01 21:28:14.380 load[11789:1435378] Load Class Child2016-02-01 21:28:14.380 load[11789:1435378] Load Class Parent+load2016-02-01 22:28:14.381 load[11789:1435378] Load Class Child+load */執行順序
load方法調用時,系統處于脆弱狀態,如果調用別的類的方法,且該方法依賴于那個類的load方法進行初始化設置,那么必須確保那個類的load方法已經調用了,比如demo中的這段代碼,打印出的字符串就為null:
// In Child.m + (void)load {NSLog(@"Load Class Child");Other *other = [Other new];[other originalFunc];// 如果不先調用other的load,下面這行代碼就無效,打印出null[Other printName]; }load方法的調用順序其實有跡可循,在Compile Sources中,文件的排放順序就是其裝載順序,自然也就是load方法調用的順序。這一點也證明了load方法中會自動調用父類的方法,因為在demo的輸出結果中,Parent的load方法先于Child調用,而它的裝載順序其實在Child之后。
雖然在這種簡單情況下我們可以辨別出各個類的load方法調用的順序,但永遠不要依賴這個順序完成你的代碼邏輯。一方面,這在后期的開發中極容易導致錯誤,另一方面,你實際上并不需要這么做。
使用場景
由于調用load方法時的環境很不安全,我們應該盡量減少load方法的邏輯。另一個原因是load方法是線程安全的,它內部使用了鎖,所以我們應該避免線程阻塞在load方法中。
一個常見的使用場景是在load方法中實現Method Swizzle:
// In Other.m + (void)load {Method originalFunc = class_getInstanceMethod([self class], @selector(originalFunc));Method swizzledFunc = class_getInstanceMethod([self class], @selector(swizzledFunc));method_exchangeImplementations(originalFunc, swizzledFunc); }在Child類的load方法中,由于還沒調用Other的load方法,所以輸出結果是"Original Output",而在main函數中,輸出結果自然就變成了"Swizzled Output"。
一般來說,除了Method Swizzle,別的邏輯都不應該放在load方法中實現。
runtime中的源碼?
load調用方式
+load是通過函數指針指向函數,拿到函數地址,分開來直接調用的,直接通過內存地址查找調用的。
initialize
這個方法在第一次給某個類發送消息時調用(比如實例化一個對象),并且只會調用一次。initialize方法實際上是一種惰性調用,也就是說如果一個類一直沒被用到,那它的initialize方法也不會被調用,這一點有利于節約資源。
調用規則
與load方法類似的是,在initialize方法內部也會調用父類的方法,而且不需要我們顯示的寫出來。與load方法不同之處在于,即使子類沒有實現initialize方法,也會調用父類的方法,這會導致一個很嚴重的問題:
// In Parent.m + (void)initialize {NSLog(@"Initialize Parent, caller Class %@", [self class]); }// In Child.m // 注釋掉initialize方法// In main.m Child *child = [Child new];運行后發現父類的initialize方法竟然調用了兩次:
2016-02-01 22:57:02.985 load[12772:1509345] Initialize Parent, caller Class Parent 2016-02-01 22:57:02.985 load[12772:1509345] Initialize Parent, caller Class Child這是因為在創建子類對象時,首先會調用一次父類的initialize方法,然后創建子類時,盡管child自己沒有實現initialize方法,但是通過obj_msgsend調用,會找到父類的方法,然后又調用了一次。
雖然initialize方法對一個類而言只會調用一次,但這里由于出現了兩個類,所以調用兩次符合規則,但不符合我們的需求。正確使用initialize方法的姿勢如下(官方文檔上這么說的):
// In Parent.m + (void)initialize {if (self == [Parent class]) {NSLog(@"Initialize Parent, caller Class %@", [self class]);} }加上判斷后,就不會因為子類而調用到自己的initialize方法了。
最后結論(參考鏈接 https://www.jianshu.com/p/c52d0b6ee5e9) :?
- 1.父類的initialize方法會比子類先執行
- 2.當子類未實現initialize方法時,會調用父類initialize方法,子類實現initialize方法時,會覆蓋父類initialize方法.
- 3.當有多個Category都實現了initialize方法,會覆蓋類中的方法,只執行一個(會執行Compile Sources 列表中最后一個Category 的initialize方法)
?
使用場景
initialize方法主要用來對一些不方便在編譯期初始化的對象進行賦值。比如NSMutableArray這種類型的實例化依賴于runtime的消息發送,所以顯然無法在編譯器初始化:
// In Parent.m static int someNumber = 0; // int類型可以在編譯期賦值 static NSMutableArray *someObjects;+ (void)initialize {if (self == [Parent class]) {// 不方便編譯期復制的對象在這里賦值someObjects = [[NSMutableArray alloc] init];} }initialize調用方式
在類第一次接收到消息時調用,消息轉發機制調用的
void _class_initialize(Class cls) {... ...// 遞歸調用父類的_class_initialize方法supercls = cls->superclass;if (supercls && !supercls->isInitialized()) {_class_initialize(supercls);}// 將要調用SEL_initializeif (PrintInitializing) {_objc_inform("INITIALIZE: calling +[%s initialize]",cls->nameForLogging());}// 通過消息機制調用SEL_initialize((void(*)(Class, SEL))objc_msgSend)(cls, SEL_initialize);// 調用完成if (PrintInitializing) {_objc_inform("INITIALIZE: finished +[%s initialize]",cls->nameForLogging());}總結
+load調用方式:+load是通過函數指針指向函數,拿到函數地址,分開來直接調用的,直接通過內存地址查找調用的。
+initialize:在類第一次接收到消息時調用,消息轉發機制調用的(objc_msgSend).?
? ? ? ? ? ? ? ? +initialize方法調用順序, 所以類別中的initialize會覆蓋主類的initialize.??
? ? ? ? ? ? ? ??如果子類沒有實現+initialize方法,會調用父類的+initialize(所以父類的+initialize方法可能會被調用多次)
?
原文鏈接:http://www.jianshu.com/p/d25f691f0b07
| ? | +(void)load | +(void)initialize |
| 執行時機 | 在程序運行后立即執行,runtime初始化中,main函數前 | 在類的方法第一次被調時執行 |
| 若自身未定義,是否沿用父類的方法? | 否 | 是 |
| 類別中的定義 | 全都執行,但后于類中的方法 | 覆蓋類中的方法,只執行一個 |
?
總結
以上是生活随笔為你收集整理的OC load 和 initialize 方法的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【深度学习】2.1深度学习的实用层面
- 下一篇: 局域网下连接其他电脑的HDFS集群