iOS開發(fā)中總能看到+load和+initialize的身影,網(wǎng)上對于這兩個方法有很多解釋,官方也有說明,但有些細(xì)節(jié)不夠清楚,今天我們來詳細(xì)扒一扒這兩個方法.
load
Apple文檔是這樣描述的
Invoked whenever a class or category is added to the Objective-C runtime; implement this method to perform class-specific behavior upon loading.
當(dāng)類(Class)或者類別(Category)加入Runtime中時(就是被引用的時候)吗跋。
實(shí)現(xiàn)該方法火窒,可以在加載時做一些類特有的操作航闺。
Discussion
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.
The order of initialization is as follows:
All initializers in any framework you link to.
調(diào)用所有的Framework中的初始化方法
All +load methods in your image.
調(diào)用所有的+load方法
All C++ static initializers and C/C++ attribute(constructor) functions in your image.
調(diào)用C++的靜態(tài)初始化方及C/C++中的attribute(constructor)函數(shù)
All initializers in frameworks that link to you.
調(diào)用所有鏈接到目標(biāo)文件的framework中的初始化方法
In addition:
A class’s +load method is called after all of its superclasses’ +load methods.
一個類的+load方法在其父類的+load方法后調(diào)用
A category +load method is called after the class’s own +load method.
一個Category的+load方法在被其擴(kuò)展的類的自有+load方法后調(diào)用
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方法中,可以安全地向同一二進(jìn)制包中的其它無關(guān)的類發(fā)送消息,但接收消息的類中的+load方法可能尚未被調(diào)用借尿。
文檔地址:https://developer.apple.com/reference/objectivec/nsobject/1418815-load?language=objc
load函數(shù)調(diào)用特點(diǎn)如下:
當(dāng)類被引用進(jìn)項(xiàng)目的時候就會執(zhí)行l(wèi)oad函數(shù)(在main函數(shù)開始執(zhí)行之前),與這個類是否被用到無關(guān),每個類的load函數(shù)只會自動調(diào)用一次.由于load函數(shù)是系統(tǒng)自動加載的民鼓,因此不需要調(diào)用父類的load函數(shù),否則父類的load函數(shù)會多次執(zhí)行霹琼。
- 1.當(dāng)父類和子類都實(shí)現(xiàn)load函數(shù)時,父類的load方法執(zhí)行順序要優(yōu)先于子類
- 2.當(dāng)子類未實(shí)現(xiàn)load方法時,不會調(diào)用父類load方法
- 3.類中的load方法執(zhí)行順序要優(yōu)先于類別(Category)
- 4.當(dāng)有多個類別(Category)都實(shí)現(xiàn)了load方法,這幾個load方法都會執(zhí)行,但執(zhí)行順序不確定(其執(zhí)行順序與類別在Compile Sources中出現(xiàn)的順序一致)
- 5.當(dāng)然當(dāng)有多個不同的類的時候,每個類load 執(zhí)行順序與其在Compile Sources出現(xiàn)的順序一致
下面通過實(shí)例來一起驗(yàn)證下:
我們新建2個類:Person繼承NSObject,Student和Teacher均繼承Person
Person : NSObject
Student : Person
Teacher : Person
新建3個Person分類:
Person (Category)
Person (Category2)
Person (Category3)
在Person,Student,Person (Category),Person (Category2),Person (Category3)的.m文件中實(shí)現(xiàn)下面方法(Teacher除外)
//In Person.m
+(void)load
{
NSLog(@"%s",__FUNCTION__);
}
//In Student.m(繼承自Person)
+(void)load
{
NSLog(@"%s",__FUNCTION__);
}
//In Teacher.m(繼承Person)
不實(shí)現(xiàn)load方法
//In Person+Category.m
+(void)load
{
NSLog(@"%s",__FUNCTION__);
}
//In Person+Category2.m
+(void)load
{
NSLog(@"%s",__FUNCTION__);
}
//In Person+Category3.m
+(void)load
{
NSLog(@"%s",__FUNCTION__);
}
運(yùn)行結(jié)果:
2017-05-04 11:11:40.612 LoadAndInitializeExample[27745:856244] +[Person load]
2017-05-04 11:11:40.615 LoadAndInitializeExample[27745:856244] +[Student load]
2017-05-04 11:11:40.616 LoadAndInitializeExample[27745:856244] +[Person(Category3) load]
2017-05-04 11:11:40.617 LoadAndInitializeExample[27745:856244] +[Person(Category) load]
2017-05-04 11:11:40.617 LoadAndInitializeExample[27745:856244] +[Person(Category2) load]
可以看到執(zhí)行順序依次為:
- 1.首先執(zhí)行的是父類Person load方法,再執(zhí)行的是子類Student load方法,說明父類的load方法執(zhí)行順序要優(yōu)先于子類
- 2.子類Teacher中沒有實(shí)現(xiàn)load方法,沒有打印,說明子類沒有實(shí)現(xiàn)load方法時并不會調(diào)用父類load方法
- 3.最后執(zhí)行的是Person 3個Category的load方法,并且沒有順序,說明類別(Category)中的load方法要晚于類,多個類別(Category)都實(shí)現(xiàn)了load方法,這幾個load方法都會執(zhí)行,但執(zhí)行順序不確定
- 4.同時我們也可以看到這幾個Category load 執(zhí)行順序與其在Compile Sources中出現(xiàn)的順序一致
- 5.當(dāng)然多個不同的類 其load執(zhí)行順序,也與其在Compile Sources出現(xiàn)的順序一致
initialize:
Apple文檔是這樣描述的
Initializes the class before it receives its first message.
在這個類接收第一條消息之前調(diào)用务傲。
Discussion
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在一個程序中每一個類的一個程序中發(fā)送一個初始化一次凉当,或是從它繼承的任何類中,都是在程序中發(fā)送第一條消息售葡。(因此看杭,當(dāng)該類不使用時,該方法可能永遠(yuǎn)不會被調(diào)用挟伙。)運(yùn)行時發(fā)送一個線程安全的方式初始化消息楼雹。父類的調(diào)用一定在子類之前。
文檔地址:https://developer.apple.com/reference/objectivec/nsobject/1418639-initialize?language=objc
initialize函數(shù)調(diào)用特點(diǎn)如下:
initialize在類或者其子類的第一個方法被調(diào)用前調(diào)用尖阔。即使類文件被引用進(jìn)項(xiàng)目,但是沒有使用,initialize不會被調(diào)用贮缅。由于是系統(tǒng)自動調(diào)用,也不需要再調(diào)用 [super initialize] 介却,否則父類的initialize會被多次執(zhí)行谴供。假如這個類放到代碼中,而這段代碼并沒有被執(zhí)行齿坷,這個函數(shù)是不會被執(zhí)行的憔鬼。
- 1.父類的initialize方法會比子類先執(zhí)行
- 2.當(dāng)子類未實(shí)現(xiàn)initialize方法時,會調(diào)用父類initialize方法,子類實(shí)現(xiàn)initialize方法時,會覆蓋父類initialize方法.
- 3.當(dāng)有多個Category都實(shí)現(xiàn)了initialize方法,會覆蓋類中的方法,只執(zhí)行一個(會執(zhí)行Compile Sources 列表中最后一個Category 的initialize方法)
我們同樣通過實(shí)例來一起驗(yàn)證下:
我們添加Person類,在.m中實(shí)現(xiàn)+ initialize方法
//In Person.m
+(void)initialize
{
NSLog(@"%s",__FUNCTION__);
}
運(yùn)行結(jié)果:
無打印...
啥也沒打印
說明:只是把類文件被引用進(jìn)項(xiàng)目,沒有使用的話,initialize不會被調(diào)用
我們在Teacher(繼承Person).m中不實(shí)現(xiàn)initialize方法
//In Person.m
+(void)initialize
{
NSLog(@"%s",__FUNCTION__);
}
//In Teacher.m(繼承自Person)
Teacher.m中不實(shí)現(xiàn)initialize方法
我們調(diào)用下Teacher的new方法,運(yùn)行
[Teacher new];
運(yùn)行結(jié)果:
2017-05-04 14:06:45.051 LoadAndInitializeExample[29238:912579] +[Person initialize]
2017-05-04 14:06:45.051 LoadAndInitializeExample[29238:912579] +[Person initialize]
運(yùn)行后發(fā)現(xiàn)父類的initialize方法竟然調(diào)用了兩次:
可見當(dāng)子類未實(shí)現(xiàn)initialize方法,會調(diào)用父類initialize方法.
但為什么會打印2次呢?
我的理解:
子類不實(shí)現(xiàn)initialize方法,會把繼承父類的initialize方法并調(diào)用一遍胃夏。在此之前,父類初始化時,會先調(diào)用一遍自己initialize方法.所以出現(xiàn)2遍,所以為了防止父類initialize中代碼多次執(zhí)行,我們應(yīng)該這樣寫:
//In Person.m
+(void)initialize
{
if(self == [Person class])
{
NSLog(@"%s",__FUNCTION__);
}
}
下面我們在 Teacher.m中實(shí)現(xiàn)initialize方法
//In Person.m
+(void)initialize
{
NSLog(@"%s",__FUNCTION__);
}
//In Teacher.m(繼承自Person)
+(void)initialize
{
NSLog(@"%s",__FUNCTION__);
}
同樣調(diào)用Teacher 的new方法,運(yùn)行
[Teacher new];
運(yùn)行結(jié)果:
2017-05-04 14:07:45.051 LoadAndInitializeExample[29238:912579] +[Person initialize]
2017-05-04 14:07:45.051 LoadAndInitializeExample[29238:912579] +[Teacher initialize]
可以看到 當(dāng)子類實(shí)現(xiàn)initialize方法后,會覆蓋父類initialize方法,這一點(diǎn)和繼承思想一樣
我們在Person.m和Person+Category.m中實(shí)現(xiàn)initialize方法
//In Person.m
+(void)initialize
{
NSLog(@"%s",__FUNCTION__);
}
//In Person+Category.m
+(void)initialize
{
NSLog(@"%s",__FUNCTION__);
}
調(diào)用Person 的 new方法,運(yùn)行
[Person new];
運(yùn)行結(jié)果:
2017-05-05 09:46:51.054 LoadAndInitializeExample[38773:1226306] +[Person(Category) initialize]
運(yùn)行后可以看到Person的initialize方法并沒有被執(zhí)行,已經(jīng)被Person+Category中的initialize取代了
當(dāng)有多個Category時會怎樣了,我們在Person,Person+Category,Person+Category2,Person+Category3 的.m中都實(shí)現(xiàn)initialize方法
//In Person.m
+(void)initialize
{
NSLog(@"%s",__FUNCTION__);
}
//In Person+Category.m
+(void)initialize
{
NSLog(@"%s",__FUNCTION__);
}
//In Person+Category2.m
+(void)initialize
{
NSLog(@"%s",__FUNCTION__);
}
//In Person+Category3.m
+(void)initialize
{
NSLog(@"%s",__FUNCTION__);
}
調(diào)用Person new方法,運(yùn)行
[Person new];
運(yùn)行結(jié)果:
2017-05-05 09:49:38.853 LoadAndInitializeExample[38825:1227819] +[Person(Category2) initialize]
可以看到,當(dāng)存在多個Category時,也只執(zhí)行一個,具體執(zhí)行哪一個Category中的initialize方法,測試后便可發(fā)現(xiàn),會執(zhí)行Compile Sources 列表中最后一個Category 的initialize方法
什么情況下使用:
+load
由于調(diào)用load方法時的環(huán)境很不安全轴或,我們應(yīng)該盡量減少load方法的邏輯。另一個原因是load方法是線程安全的仰禀,它內(nèi)部使用了鎖照雁,所以我們應(yīng)該避免線程阻塞在load方法中
load很常見的一個使用場景,交換兩個方法的實(shí)現(xiàn)
//摘自MJRefresh
+ (void)load
{
[self exchangeInstanceMethod1:@selector(reloadData) method2:@selector(mj_reloadData)];
[self exchangeInstanceMethod1:@selector(reloadRowsAtIndexPaths:withRowAnimation:) method2:@selector(mj_reloadRowsAtIndexPaths:withRowAnimation:)];
[self exchangeInstanceMethod1:@selector(deleteRowsAtIndexPaths:withRowAnimation:) method2:@selector(mj_deleteRowsAtIndexPaths:withRowAnimation:)];
[self exchangeInstanceMethod1:@selector(insertRowsAtIndexPaths:withRowAnimation:) method2:@selector(mj_insertRowsAtIndexPaths:withRowAnimation:)];
[self exchangeInstanceMethod1:@selector(reloadSections:withRowAnimation:) method2:@selector(mj_reloadSections:withRowAnimation:)];
[self exchangeInstanceMethod1:@selector(deleteSections:withRowAnimation:) method2:@selector(mj_deleteSections:withRowAnimation:)];
[self exchangeInstanceMethod1:@selector(insertSections:withRowAnimation:) method2:@selector(mj_insertSections:withRowAnimation:)];
}
+ (void)exchangeInstanceMethod1:(SEL)method1 method2:(SEL)method2
{
method_exchangeImplementations(class_getInstanceMethod(self, method1), class_getInstanceMethod(self, method2));
}
+initialize
initialize方法主要用來對一些不方便在編譯期初始化的對象進(jìn)行賦值。比如NSMutableArray這種類型的實(shí)例化依賴于runtime的消息發(fā)送答恶,所以顯然無法在編譯器初始化:
// In Person.m
// int類型可以在編譯期賦值
static int someNumber = 0;
static NSMutableArray *someArray;
+ (void)initialize {
if (self == [Person class]) {
// 不方便編譯期復(fù)制的對象在這里賦值
someArray = [[NSMutableArray alloc] init];
}
}
總結(jié):
load和initialize的共同點(diǎn)
1.如果父類和子類都被調(diào)用,父類的調(diào)用一定在子類之前
+load方法要點(diǎn)
當(dāng)類被引用進(jìn)項(xiàng)目的時候就會執(zhí)行l(wèi)oad函數(shù)(在main函數(shù)開始執(zhí)行之前),與這個類是否被用到無關(guān),每個類的load函數(shù)只會自動調(diào)用一次.由于load函數(shù)是系統(tǒng)自動加載的饺蚊,因此不需要再調(diào)用[super load],否則父類的load函數(shù)會多次執(zhí)行悬嗓。
- 1.當(dāng)父類和子類都實(shí)現(xiàn)load函數(shù)時,父類的load方法執(zhí)行順序要優(yōu)先于子類
- 2.當(dāng)一個類未實(shí)現(xiàn)load方法時,不會調(diào)用父類load方法
- 3.類中的load方法執(zhí)行順序要優(yōu)先于類別(Category)
- 4.當(dāng)有多個類別(Category)都實(shí)現(xiàn)了load方法,這幾個load方法都會執(zhí)行,但執(zhí)行順序不確定(其執(zhí)行順序與類別在Compile Sources中出現(xiàn)的順序一致)
- 5.當(dāng)然當(dāng)有多個不同的類的時候,每個類load 執(zhí)行順序與其在Compile Sources出現(xiàn)的順序一致
注意:
load調(diào)用時機(jī)比較早,當(dāng)load調(diào)用時,其他類可能還沒加載完成,運(yùn)行環(huán)境不安全.
load方法是線程安全的污呼,它使用了鎖,我們應(yīng)該避免線程阻塞在load方法.
+initialize方法要點(diǎn)
initialize在類或者其子類的第一個方法被調(diào)用前調(diào)用包竹。即使類文件被引用進(jìn)項(xiàng)目,但是沒有使用,initialize不會被調(diào)用燕酷。由于是系統(tǒng)自動調(diào)用,也不需要顯式的調(diào)用父類的initialize周瞎,否則父類的initialize會被多次執(zhí)行苗缩。假如這個類放到代碼中,而這段代碼并沒有被執(zhí)行声诸,這個函數(shù)是不會被執(zhí)行的酱讶。
- 1.父類的initialize方法會比子類先執(zhí)行
- 2.當(dāng)子類不實(shí)現(xiàn)initialize方法,會把父類的實(shí)現(xiàn)繼承過來調(diào)用一遍彼乌。在此之前泻肯,父類的方法會被優(yōu)先調(diào)用一次
- 3.當(dāng)有多個Category都實(shí)現(xiàn)了initialize方法,會覆蓋類中的方法,只執(zhí)行一個(會執(zhí)行Compile Sources 列表中最后一個Category 的initialize方法)
注意:
在initialize方法收到調(diào)用時,運(yùn)行環(huán)境基本健全渊迁。
initialize內(nèi)部也使用了鎖,所以是線程安全的灶挟。但同時要避免阻塞線程宫纬,不要再使用鎖
以上總結(jié)可能并不完全,歡迎留言補(bǔ)充.....
代碼地址:https://github.com/CoderZhuXH/MyBlogExample