load方法的本質(zhì)是什么椿猎?initialize呢?兩個有什么區(qū)別?
- 最好的研究方法就是實踐:新建兩個類實現(xiàn)
load
方法
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface SFPerson : NSObject
@end
NS_ASSUME_NONNULL_END
#import "SFPerson.h"
@implementation SFPerson
+ (void)load{
NSLog(@"SFPerson +load");
}
@end
#import "SFPerson.h"
NS_ASSUME_NONNULL_BEGIN
@interface SFPerson (Helper)
@end
NS_ASSUME_NONNULL_END
#import "SFPerson+Helper.h"
@implementation SFPerson (Helper)
+ (void)load{
NSLog(@"SFPerson (Helper) + load");
}
@end
#import "SFPerson.h"
NS_ASSUME_NONNULL_BEGIN
@interface SFPerson (Addtion)
@end
NS_ASSUME_NONNULL_END
#import "SFPerson+Addtion.h"
@implementation SFPerson (Addtion)
+ (void)load{
NSLog(@"SFPerson (Addtion) + load");
}
@end
2019-08-27 14:16:08.683691+0800 load[33088:238375] SFPerson +load
2019-08-27 14:16:08.684128+0800 load[33088:238375] SFPerson (Helper) + load
2019-08-27 14:38:15.283433+0800 load[33282:256018] SFPerson (Addtion) + load
直接運行后出現(xiàn)上面的打印輸出暖庄,我們沒有導(dǎo)入頭文件,也沒有進行創(chuàng)建對象調(diào)用楼肪,系統(tǒng)直接調(diào)用load方法由此可見:
分類中也存在load
方法培廓,load
方法是在程序啟動時,加載類春叫、分類的時候就會調(diào)用肩钠。在調(diào)用分類的load
方法前會優(yōu)先調(diào)用本類的load
方法。分類中的load
方法誰先編譯誰先調(diào)用
我們也可以通過源碼驗證如下:
可以看到類的load方法調(diào)用一次就不會調(diào)用了
loadable_classes_user>0
這樣循環(huán)就不會再次執(zhí)行了繼續(xù)查看
call_class_loads()
類方法
static void call_class_loads(void)
{
int i;
// Detach current loadable list.
struct loadable_class *classes = loadable_classes;
int used = loadable_classes_used;
loadable_classes = nil;
loadable_classes_allocated = 0;
loadable_classes_used = 0;
// Call all +loads for the detached list.
for (i = 0; i < used; i++) {
直接通過數(shù)組的下標得到我們需要的方法
Class cls = classes[i].cls;
load_method_t load_method = (load_method_t)classes[i].method;
if (!cls) continue;
if (PrintLoading) {
_objc_inform("LOAD: +[%s load]\n", cls->nameForLogging());
}
(*load_method)(cls, SEL_load);
}
// Destroy the detached list.
if (classes) free(classes);
}
和call_category_loads()
分類方法的實現(xiàn)
static bool call_category_loads(void)
{
int i, shift;
bool new_categories_added = NO;
// Detach current loadable list.
struct loadable_category *cats = loadable_categories;
int used = loadable_categories_used;
int allocated = loadable_categories_allocated;
loadable_categories = nil;
loadable_categories_allocated = 0;
loadable_categories_used = 0;
// Call all +loads for the detached list.
for (i = 0; i < used; i++) {
Category cat = cats[i].cat;
load_method_t load_method = (load_method_t)cats[i].method;
Class cls;
if (!cat) continue;
cls = _category_getClass(cat);
if (cls && cls->isLoadable()) {
if (PrintLoading) {
_objc_inform("LOAD: +[%s(%s) load]\n",
cls->nameForLogging(),
_category_getName(cat));
}
(*load_method)(cls, SEL_load);
cats[i].cat = nil;
}
}
// Compact detached list (order-preserving)
shift = 0;
for (i = 0; i < used; i++) {
if (cats[i].cat) {
cats[i-shift] = cats[i];
} else {
shift++;
}
}
used -= shift;
// Copy any new +load candidates from the new list to the detached list.
new_categories_added = (loadable_categories_used > 0);
for (i = 0; i < loadable_categories_used; i++) {
if (used == allocated) {
allocated = allocated*2 + 16;
cats = (struct loadable_category *)
realloc(cats, allocated *
sizeof(struct loadable_category));
}
cats[used++] = loadable_categories[i];
}
// Destroy the new list.
if (loadable_categories) free(loadable_categories);
// Reattach the (now augmented) detached list.
// But if there's nothing left to load, destroy the list.
if (used) {
loadable_categories = cats;
loadable_categories_used = used;
loadable_categories_allocated = allocated;
} else {
if (cats) free(cats);
loadable_categories = nil;
loadable_categories_used = 0;
loadable_categories_allocated = 0;
}
if (PrintLoading) {
if (loadable_categories_used != 0) {
_objc_inform("LOAD: %d categories still waiting for +load\n",
loadable_categories_used);
}
}
return new_categories_added;
}
我們看到無論是分類還是類都是通過找到下標直接調(diào)用的沒有通過
msgSend消息轉(zhuǎn)發(fā)
暂殖,所以load
方法的本質(zhì)調(diào)用為:load根據(jù)函數(shù)地址直接調(diào)用价匠,在runtime加載類分類的時候調(diào)用,只會調(diào)用一次呛每,先調(diào)用類的load方法再去調(diào)用分類的load踩窖,先編譯的類,先調(diào)用晨横,調(diào)用子類的load之前洋腮,先調(diào)用父類的load方法
我們再添加一個
SFStudent
類繼承SFPerson
分別實現(xiàn)一個類方法run
方法
#import "SFPerson.h"
@implementation SFPerson
+ (void)load{
NSLog(@"SFPerson +load");
}
+(void)run{
NSLog(@"SFPerson +run");
}
@end
利用[SFStudent run];
進行調(diào)用輸入如下:
2019-08-27 14:45:34.859386+0800 load[33449:263656] SFPerson +load
2019-08-27 14:45:34.859677+0800 load[33449:263656] SFPerson (Helper) + load
2019-08-27 14:45:34.859693+0800 load[33449:263656] SFPerson (Addtion) + load
2019-08-27 14:45:34.859769+0800 load[33449:263656] SFPerson (Addtion) +run
由此可知:分類中重寫類方法時箫柳,分類的類方法會優(yōu)先調(diào)用。我們利用SFStudent
調(diào)用run
方法時啥供,輸出為SFPerson (Addtion) +run
優(yōu)先調(diào)用的原因
下面繼續(xù)initialize
分析:
- 我們在
SFPerson
中添加+(void)initialize
方法
#import "SFPerson.h"
@implementation SFPerson
+(void)initialize{
NSLog(@" SFPerson initialize ");
}
@end
在main.m
中調(diào)用如下
#import <Foundation/Foundation.h>
#import "SFStudent.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
[SFStudent run];
}
return 0;
}
2019-08-27 15:39:29.802479+0800 load[39941:335527] SFPerson initialize
2019-08-27 15:39:29.802491+0800 load[39941:335527] SFPerson initialize
- 我們看到調(diào)用了兩次
initialize
方法悯恍,為什么呢?
#import <Foundation/Foundation.h>
#import "SFStudent.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
[SFStudent run];
[SFStudent run];
[SFStudent run];
}
return 0;
}
2019-08-27 16:20:53.860266+0800 load[40134:360148] SFPerson initialize
2019-08-27 16:20:53.860282+0800 load[40134:360148] SFPerson initialize
-
多次調(diào)用的輸入結(jié)果和上面的一樣
initialize方法會在類第一次接收消息的時候調(diào)用 調(diào)用順序:先調(diào)用父類的+initialize伙狐,在調(diào)用子類的+initialize
無論多少次調(diào)用都執(zhí)行一次涮毫,是類第一次接收到消息的時候調(diào)用,每一個類只會initialize一次(父類的initialize方法可能會被調(diào)用多次)
查看源碼:
9FF98C1983E5B2CA36457C94BE87F727.png
查看源碼可以看到initialize方法是通過消息轉(zhuǎn)發(fā)機制實現(xiàn)的
void callInitialize(Class cls)
{
((void(*)(Class, SEL))objc_msgSend)(cls, SEL_initialize);
asm("");
}
調(diào)用時間:
load
方法是可以繼承的贷屎,但是一般情況下窒百,都是不會主動調(diào)用load
方法,都是讓系統(tǒng)自動調(diào)用+ initialize
方法會在類第一次接收消息的時候調(diào)用
調(diào)用順序:- 先調(diào)用父類的
+initialize
豫尽,在調(diào)用子類+initialize
父類的初始化方法可能調(diào)用多次, 先初始化父類再初始化子類篙梢,而且每個類只會初始化一次, 如果分類實現(xiàn)了+initialize
,就覆蓋類本身的+initialize
方法- 先調(diào)用類的
load
方法再去調(diào)用分類的load
美旧,先編譯的類渤滞,先調(diào)用,調(diào)用子類的load
之前榴嗅,先調(diào)用父類的load
調(diào)用方式的:+initialize
是通過objc_msgSend
進行調(diào)用的load
根據(jù)函數(shù)地址直接調(diào)用妄呕,在runtime
加載類分類的時候調(diào)用,只會調(diào)用一次