+load
和 +initialize
都是用于類(lèi)的初始化,但是這兩個(gè)看是簡(jiǎn)單又相似的類(lèi)方法讶泰,在許多方面讓人感到困惑咏瑟,比如:
- 子類(lèi)、父類(lèi)痪署、分類(lèi)中相應(yīng)方法什么時(shí)候會(huì)被調(diào)用
- 子類(lèi)中需要顯示的調(diào)用父類(lèi)的實(shí)現(xiàn)嗎响蕴?
- 每個(gè)方法只調(diào)用一次,還是多次惠桃?
一. 實(shí)例驗(yàn)證:
舉個(gè)?? :
+load方法:
在 main 函數(shù)中打印當(dāng)前 函數(shù)名稱(chēng):
#import <UIKit/UIKit.h>
#import "AppDelegate.h"
int main(int argc, char * argv[]) {
NSLog(@"%s",__func__);
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
同時(shí)定義Person
類(lèi)和Son
類(lèi)(Son
類(lèi)繼承Person
類(lèi)):
Person類(lèi):
#import <Foundation/Foundation.h>
@interface Person : NSObject
@end
#import "Person.h"
@implementation Person
+ (void)load{
NSLog(@"%s",__func__);
}
+ (void)initialize{
[super initialize];
NSLog(@"%s %@",__func__,[self class]);
}
- (instancetype)init{
if (self = [super init]) {
NSLog(@"%s",__func__);
}
return self;
}
@end
Son類(lèi):
#import "Person.h"
@interface Son : Person
@end
#import "Son.h"
@implementation Son
+ (void)load{
NSLog(@"%s",__func__);
}
+ (void)initialize{
[super initialize];
NSLog(@"%s %@",__func__,[self class]);
}
- (instancetype)init{
if (self = [super init]) {
NSLog(@"%s",__func__);
}
return self;
}
@end
運(yùn)行輸出:
FJTestProject[29237:1018755] +[Person load]
FJTestProject[29237:1018755] +[Son load]
FJTestProject[29237:1018755] main
從輸出結(jié)果可以看出,在沒(méi)有對(duì)類(lèi)進(jìn)行任何操作的情況下,+load
方法會(huì)被默認(rèn)執(zhí)行辜王,并且是在main
函數(shù)之前執(zhí)行劈狐。
+initialize方法:
同時(shí)我們查看下+initialize
方法:
#import "Son.h"
#import "Person.h"
#import "ViewController.h"
@interface ViewController ()
@end
@implementation ViewController
#pragma mark --- init method
#pragma mark --- life circle
- (void)viewDidLoad {
[super viewDidLoad];
Person *aPerson = [Person new];
Son *bSon = [Son new];
}
@end
輸出日志:
FJTestProject[29627:1058200] +[Person load]
FJTestProject[29627:1058200] +[Son load]
FJTestProject[29627:1058200] main
FJTestProject[29627:1058200] +[Person initialize] Person
FJTestProject[29627:1058200] -[Person init]
FJTestProject[29627:1058200] +[Person initialize] Son
FJTestProject[29627:1058200] +[Son initialize] Son
FJTestProject[29627:1058200] -[Person init]
FJTestProject[29627:1058200] -[Son init]
從輸出內(nèi)容可以看出:
+initialize
是通過(guò)類(lèi)似懶加載調(diào)用的,如果沒(méi)有使用這個(gè)類(lèi)呐馆,系統(tǒng)默認(rèn)不會(huì)去掉用這個(gè)方法肥缔,且默認(rèn)只加載一次+initialize
的調(diào)用發(fā)生在+init
方法之前,創(chuàng)建子類(lèi)的時(shí)候會(huì)去調(diào)用父類(lèi)的+ initialize
方法汹来。
category 調(diào)用順序:
首先為Person
類(lèi)添加類(lèi)別:
#import "Person+Extention.h"
@implementation Person (Extention)
+ (void)load{
NSLog(@"%s",__func__);
}
+ (void)initialize{
[super initialize];
NSLog(@"%s %@",__func__,[self class]);
}
@end
運(yùn)行程序续膳,日志如下:
FJTestProject[29751:1066412] +[Person load]
FJTestProject[29751:1066412] +[Son load]
FJTestProject[29751:1066412] +[Person(Extention) load]
FJTestProject[29751:1066412] main
FJTestProject[29751:1066412] +[Person(Extention) initialize] Person
FJTestProject[29751:1066412] -[Person init]
FJTestProject[29751:1066412] +[Person(Extention) initialize] Son
FJTestProject[29751:1066412] +[Son initialize] Son
FJTestProject[29751:1066412] -[Person init]
FJTestProject[29751:1066412] -[Son init]
從日志我們可以看出:
對(duì)于+load
方法:
- 會(huì)先執(zhí)行父類(lèi)中的
load
方法,再執(zhí)行子類(lèi)中的load
方法收班,最后在執(zhí)行類(lèi)別的load
方法坟岔。
對(duì)于+ initialize
方法:
- 類(lèi)別會(huì)覆蓋類(lèi)中的方法,只執(zhí)行分類(lèi)的實(shí)現(xiàn)摔桦。
二. 分析
+ load
+load
方法是當(dāng)類(lèi)或分類(lèi)被添加到Objective-C runtime
時(shí)被調(diào)用社付,實(shí)現(xiàn)這個(gè)方法可以讓我們?cè)陬?lèi)加載的時(shí)候執(zhí)行一些類(lèi)相關(guān)的行為。子類(lèi)的
+load
方法會(huì)在它的所有父類(lèi)的+load
方法之后執(zhí)行邻耕,分類(lèi)的
+load
方法會(huì)在它的主類(lèi)的+load
方法之后執(zhí)行鸥咖。但是不同類(lèi)之間的
+load
方法的調(diào)用順序是不確定的。
接著我們打開(kāi)runtime
工程兄世,在objc-runtime-new.mm
中我們來(lái)看與+
load方法相關(guān)的關(guān)鍵函數(shù)啼辣。
首先, void prepare_load_methods(header_info *hi)
函數(shù):
void prepare_load_methods(header_info *hi)
{
size_t count, i;
rwlock_assert_writing(&runtimeLock);
classref_t *classlist =
_getObjc2NonlazyClassList(hi, &count);
for (i = 0; i < count; i++) {
schedule_class_load(remapClass(classlist[i]));
}
category_t **categorylist = _getObjc2NonlazyCategoryList(hi, &count);
for (i = 0; i < count; i++) {
category_t *cat = categorylist[i];
Class cls = remapClass(cat->cls);
if (!cls) continue; // category for ignored weak-linked class
realizeClass(cls);
assert(cls->ISA()->isRealized());
add_category_to_loadable_list(cat);
}
}
這個(gè)函數(shù)的作用就是提前準(zhǔn)備好滿足+load
方法調(diào)用條件的類(lèi)和分類(lèi),以供接下來(lái)調(diào)用御滩。其中鸥拧,在處理類(lèi)時(shí),調(diào)用了同文件中的另一個(gè)函數(shù)static void schedule_class_load(Class cls)
來(lái)執(zhí)行具體的操作艾恼。
static void schedule_class_load(Class cls)
{
if (!cls) return;
assert(cls->isRealized()); // _read_images should realize
if (cls->data()->flags & RW_LOADED) return;
// Ensure superclass-first ordering
schedule_class_load(cls->superclass);
add_class_to_loadable_list(cls);
cls->setInfo(RW_LOADED);
}
該函數(shù)中的schedule_class_load(cls->superclass);
住涉,對(duì)入?yún)⒌母割?lèi)進(jìn)行了遞歸調(diào)用,以確保父類(lèi)優(yōu)先的順序钠绍。
當(dāng)void prepare_load_methods(header_info *hi)
函數(shù)執(zhí)行完后舆声,當(dāng)前所有滿足+load
方法調(diào)用條件的類(lèi)和分類(lèi)就被分別存放在全局變量loadable_classes
和loadable_categories
中了。
準(zhǔn)備好類(lèi)和分類(lèi)后柳爽,接下來(lái)就是對(duì)它們的+load
方法進(jìn)行調(diào)用媳握。打開(kāi)文件objc-loadmethod.m
,找到void call_load_methods(void)
函數(shù)。
void call_load_methods(void)
{
static BOOL loading = NO;
BOOL more_categories;
recursive_mutex_assert_locked(&loadMethodLock);
// Re-entrant calls do nothing; the outermost call will finish the job.
if (loading) return;
loading = YES;
void *pool = objc_autoreleasePoolPush();
do {
// 1. Repeatedly call class +loads until there aren't any more
while (loadable_classes_used > 0) {
call_class_loads();
}
// 2. Call category +loads ONCE
more_categories = call_category_loads();
// 3. Run more +loads if there are classes OR more untried categories
} while (loadable_classes_used > 0 || more_categories);
objc_autoreleasePoolPop(pool);
loading = NO;
}
這個(gè)函數(shù)的作用就是調(diào)用上一步準(zhǔn)備好的類(lèi)和分類(lèi)中的+load方法磷脯,并且確保類(lèi)優(yōu)先于分類(lèi)的順序蛾找。我們繼續(xù)查看在這個(gè)函數(shù)中調(diào)用另外兩個(gè)關(guān)鍵函數(shù)static void call_class_loads(void)
和 static BOOL call_category_loads(void)
。由于這兩個(gè)函數(shù)的作用大同小異赵誓,下面以static void call_class_loads(void)
函數(shù)為例進(jìn)行探討打毛。
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++) {
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_internal(classes);
}
這個(gè)函數(shù)的作用就是真正負(fù)責(zé)調(diào)用類(lèi)的+load
方法柿赊,它從全局變量loadable_classes
中取出所有可供調(diào)用的類(lèi),并進(jìn)行清零操作幻枉。
loadable_classes = nil;
loadable_classes_allocated = 0;
loadable_classes_used = 0;
-
loadable_classes
指向用于保存類(lèi)信息的內(nèi)存首地址 -
loadable_classes_allocated
標(biāo)識(shí)已分配的內(nèi)存空間大小 -
loadable_classes_used
則標(biāo)識(shí)已使用的內(nèi)存空間大小碰声。
然后,循環(huán)調(diào)用所有類(lèi)的+load
方法熬甫。注意胰挑,這里是(調(diào)用分類(lèi)的+load
方法也是如此)直接使用函數(shù)內(nèi)存地址的方式(*load_method)(cls, SEL_load);
對(duì)+load
方法進(jìn)行調(diào)用的,而不是使用發(fā)送消息objc_msgSend
的方式椿肩。
這樣的調(diào)用方式就使得+load
方法擁有了一個(gè)非常有趣的特性瞻颂,那就是子類(lèi)、父類(lèi)和分類(lèi)中的+load
方法的實(shí)現(xiàn)是被區(qū)別對(duì)待的郑象。也就是說(shuō)如果子類(lèi)沒(méi)有實(shí)現(xiàn)+load
方法贡这,那么當(dāng)它被加載時(shí)runtime
是不會(huì)去調(diào)用父類(lèi)的+load
方法的。同理扣唱,當(dāng)一個(gè)類(lèi)和它的分類(lèi)都實(shí)現(xiàn)+load
方法時(shí)藕坯,兩個(gè)方法都會(huì)被調(diào)用。因此噪沙,我們常沉侗耄可以利用這個(gè)特性做一些"邪惡"的事情比如說(shuō)方法混淆(Method Swizzling)
+initialize
-
+iniitialize
方法是在類(lèi)或它的子類(lèi)收到第一條消息之前被調(diào)用的,這里所指的消息包括實(shí)例方法和類(lèi)方法的調(diào)用正歼。也就是說(shuō)+initialize
方法是以懶加載的方式被調(diào)用辐马,如果程序一直沒(méi)有給某個(gè)類(lèi)或它的子類(lèi)發(fā)送消息,那么這個(gè)類(lèi)的+initialize
方法是永遠(yuǎn)不會(huì)被調(diào)用的局义。這樣有利于節(jié)省系統(tǒng)資源喜爷,避免浪費(fèi)。
同樣萄唇,我們看下runtime
的源碼來(lái)理解+initialize方法的理解檩帐。打開(kāi)文件objc-runtime-new.mm
,找到lookUpImpOrForward
函數(shù):
IMP lookUpImpOrForward(Class cls, SEL sel, id inst,
bool initialize, bool cache, bool resolver)
{
...
rwlock_unlock_write(&runtimeLock);
}
if (initialize && !cls->isInitialized()) {
_class_initialize (_class_getNonMetaClass(cls, inst));
// 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
}
// The lock is held to make method-lookup + cache-fill atomic
// with respect to method addition. Otherwise, a category could
...
}
當(dāng)我們給某個(gè)類(lèi)發(fā)送消息時(shí)另萤,runtime
會(huì)調(diào)用這個(gè)函數(shù)在類(lèi)中查找相應(yīng)方法的實(shí)現(xiàn)或進(jìn)行消息轉(zhuǎn)發(fā)湃密。從 if (initialize && !cls->isInitialized())
判斷我們可以看出,當(dāng)類(lèi)沒(méi)有初始化時(shí)runtime
會(huì)調(diào)用void _class_initialize(Class cls)
函數(shù)對(duì)該類(lèi)進(jìn)行初始化四敞。
void _class_initialize(Class cls)
{
...
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_enter(&classInitLock);
if (!cls->isInitialized() && !cls->isInitializing()) {
cls->setInitializing();
reallyInitialize = YES;
}
monitor_exit(&classInitLock);
if (reallyInitialize) {
// We successfully set the CLS_INITIALIZING bit. Initialize the class.
// Record that we're initializing this class so we can message it.
_setThisThreadIsInitializingClass(cls);
// Send the +initialize message.
// Note that +initialize is sent to the superclass (again) if
// this class doesn't implement +initialize. 2157218
if (PrintInitializing) {
_objc_inform("INITIALIZE: calling +[%s initialize]",
cls->nameForLogging());
}
((void(*)(Class, SEL))objc_msgSend)(cls, SEL_initialize);
if (PrintInitializing) {
_objc_inform("INITIALIZE: finished +[%s initialize]",
...
}
-
其中泛源,
supercls = cls->superclass; if (supercls && !supercls->isInitialized()) { _class_initialize(supercls); }
對(duì)入?yún)⒌母割?lèi)進(jìn)行了遞歸調(diào)用,以保證父類(lèi)優(yōu)先于子類(lèi)初始化忿危。
另外达箍,最關(guān)鍵的是
((void(*)(Class, SEL))objc_msgSend)(cls, SEL_initialize);
這行代碼暴露了+initialize
方法的本質(zhì),也就是說(shuō)runtime使用了發(fā)送消息objc_msgSend
的方式對(duì)+initialize
方法進(jìn)行調(diào)用铺厨。也就是說(shuō)+initialize
方法的調(diào)用與普通方法的調(diào)用是一致的缎玫,走得都是發(fā)送消息的流程硬纤。換言之,如果子類(lèi)沒(méi)有實(shí)現(xiàn)
+initialize
方法碘梢,那么繼承自父類(lèi)的實(shí)現(xiàn)會(huì)被調(diào)用咬摇,如果一個(gè)分類(lèi)實(shí)現(xiàn)了+initialize
方法,那么就會(huì)對(duì)這個(gè)類(lèi)中的實(shí)現(xiàn)造成覆蓋煞躬。
因此,如果一個(gè)子類(lèi)沒(méi)有實(shí)現(xiàn)+initialize
方法逸邦,那么父類(lèi)的實(shí)現(xiàn)會(huì)被執(zhí)行多次恩沛,有時(shí)候,這可能不是你想要的缕减;但是如果我們想確保每個(gè)類(lèi)的+initialize
方法只執(zhí)行一次雷客,避免多次執(zhí)行可能帶來(lái)的副作用時(shí),我們可以使用如下代碼:
+ (void)initialize {
if (self == [ClassName self]) {
// ... do the initialization ...
}
}
三.總結(jié)
通過(guò)閱讀runtime
的源碼桥狡,我們知道了+load
和 +initialize
方法實(shí)現(xiàn)的細(xì)節(jié)搅裙,明白了它們的調(diào)用機(jī)制和各自的特點(diǎn)。下面進(jìn)行各方面對(duì)比:
+load VS +initialize
調(diào)用時(shí)機(jī): 被添加到 runtime 時(shí) VS 收到第一條消息前裹芝,可能永遠(yuǎn)不調(diào)用
調(diào)用順序: 父類(lèi)->子類(lèi)->分類(lèi) VS 父類(lèi)->子類(lèi)
調(diào)用次數(shù): 1次 VS 多次
是否需要顯式調(diào)用父類(lèi)實(shí)現(xiàn): 否 VS 否
是否沿用父類(lèi)的實(shí)現(xiàn): 否 VS 是
分類(lèi)中的實(shí)現(xiàn): 類(lèi)和分類(lèi)都執(zhí)行 VS 覆蓋類(lèi)中的方法,只執(zhí)行分類(lèi)的實(shí)現(xiàn)