NSObject類作爲Objective-C中絕大多數類的父類,向其子類提供了基本的Runtime接口與Objective-C Class的一些方法默認實現。
在NSObject中有兩個類方法,load與initialize方法,由Runtime動態調用,用於配置Class或Category。(這種配置對於所有的Class實例,均有效)
在Apple文檔中,它們被分類爲Initializing a Class方法。
+initialize
Initializes the class before it receives its first message.
+load
Invoked whenever a class or category is added to the Objective-C runtime; implement this method to perform class-specific behavior upon loading.
load方法
在程序運行時,Runtime會將所有的Class和Category加載到內存中,這時,會調用類的load方法,通知我們Class或Category已經被加載到內存中。
+(void)load;
在Objective-C中,初始化的順序爲
The order of initialization is as follows:
- All initializers in any framework you link to.
- All +load methods in your image.
- All C++ static initializers and C/C++ attribute(constructor)
functions in your image.
- All initializers in frameworks that link to you.
其中對於Objective-C的Class與Category的load方法的調用順序爲:
- superclass在subclass前調用load
- category在它所擴展的類之後被調用load方法
注意,在load方法中,你可以向其他類發送消息(即方法調用),Objective-C會保證消息發送成功。但是並不保證接受消息的load函數已經被調用。
initialize方法
initialize方法是用來初始化一個Class或Category的另一個方法。它與load方法調用的不同之處在於,它會在該Class或其subclass 接受代碼中第一個消息之前時候被調用。如果subclass沒有重寫initialize方法的話,則super class的initialize函數會被多次調用。
而superclass會在subclass之前被調用initialize方法。
+(void)initialize;
對於initialize方法,需要注意兩點:
-
Runtime對於initialize方法的調用是線程安全的(注意這裏並沒有包含並自己顯示調用的情況),這意味着:
(1)initialize會在class接受第一個消息的線程中被調用。
(2)initialize會阻塞class的message sending,只有當initialize方法執行完畢,其餘的message纔會被執行。這裏有dead lock的危險,因此在initialize中的代碼不應過於複雜。 -
同一個class的initialize方法可能會被多次調用,這種情況發生在subclass沒有實現initialize方法的時候或者顯示調用[super initialize]時。
爲了防止initialize多次調用,可以在initialize方法中加入判斷當前調用者是否爲當前類本身。
+(void)initialize {
if (self == [ClassName self]) {
// ... do the initialization ...
}
}
load VS initialize
load 與 initialize 方法是沒有先後關係的。
它們調用的時機不一樣:
- load當runtime將類加載時被調用,而且這個時機要先於appdelegate中的
(BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions - initialize方法發生在類第一次接受消息時調用。關於這裏的第一個消息需要特別說明一下,對於 NSObject 的 runtime 機制而言,其在調用 NSObject 的 + (void)load 消息不被視爲第一個消息,但是,如果像普通函數調用一樣直接調用 NSObject 的 + (void)load 消息,則會引起 + (void)initialize 的調用。反之,如果沒有向 NSObject 發送第一個消息,+ (void)initialize 則不會被自動調用。
load 與 initialize的繼承性不一樣
- load方法針對於每一個類而言,它不會被繼承。如果子類沒有重寫load方法,也不會調用superclass的load方法。
- initialize 方法可以被繼承,當subclass沒有重寫initialize方法時,如果superclass中有實現,則會調用之。
- 對於Category,會覆蓋其對應類的initialize方法。
- 當一個類被runtime加載時,只會調用其自身的load方法,其superclass的load方法不會被調用。而當initialize被調用時,其superclass的initialize函數會被依次調用。如下代碼
@interface Car : NSObject
@end
@interface AutoCar : Car
@end
@interface BMWCar : AutoCar
@end
@interface BMWCar(MyCar)
@end
@implementation Car
+(void) initialize
{
NSLog(@"%@ %s", [self class], __FUNCTION__);
}
@end
@implementation AutoCar
+(void) initialize
{
NSLog(@"%@ %s", [self class], __FUNCTION__);
}
@end
@implementation BMWCar
+(void) initialize
{
NSLog(@"%@ %s", [self class], __FUNCTION__);
}
@end
- (void)viewDidLoad {
[super viewDidLoad];
BMWCar *myCar = [BMWCar new];
}
輸出結果爲:
Car +[Car initialize]
AutoCar +[AutoCar initialize]
BMWCar +[BMWCar initialize]
參考文章
應用實例
Method Swizzling 和 AOP 實踐
Notification Once
補充:一種另類的單例實現
突然想到了一個問題,既然load方法與initialize方法都會被Runtime自動調用一次,並且在Runtime情況下,這兩個方法都是線程安全的,那麼是否可以作爲單例類的一個實現呢?
不廢話,上代碼:
@interface MySpecialObject : NSObject
+(MySpecialObject *) sharedInstance;
@end
// 利用initialize函數實現
static MySpecialObject *singalObject = nil;
@implementation MySpecialObject
+(void) initialize
{
if (self == [MySpecialObject self] && singalObject == nil) {
singalObject = [[MySpecialObject alloc] init];
}
}
+(MySpecialObject *) sharedInstance
{
return singalObject;
}
- (void)viewDidLoad {
[super viewDidLoad];
MySpecialObject *obj = [MySpecialObject sharedInstance];
NSLog(@"%@", obj);
}
-(void) viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
MySpecialObject *obj = [MySpecialObject sharedInstance];
NSLog(@"%@", obj);
}
-(void) viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
MySpecialObject *obj = [MySpecialObject sharedInstance];
NSLog(@"%@", obj);
}
輸出:
2016-08-13 16:13:07.951 SpecialSingalObject[1304:110175] <MySpecialObject: 0x7fc5d849f400>
2016-08-13 16:13:08.826 SpecialSingalObject[1304:110175] <MySpecialObject: 0x7fc5d849f400>
2016-08-13 16:13:09.556 SpecialSingalObject[1304:110175] <MySpecialObject: 0x7fc5d849f400>
確實,得到了同一個實例。
對於initialize 函數,的解釋:
(1) if (self == [MySpecialObject self] …
是爲了保證 initialize函數只有在本類而非subclass時才執行單例初始化函數。
(2)if(…&& singalObject == nil) 是爲了防止initialize多次調用而產生多個實例(除了Runtime調用,我們也可以顯示調用initialize方法)。經過測試,當我們將initialize方法本身作爲class的第一個方法執行時,Runtime的initialize會被先調用(這保證了線程安全),然後我們自己顯示調用的initialize函數再被調用。
由於initialize方法的第一次調用一定是Runtime調用,而Runtime又保證了線程安全性,因此這裏只簡單的檢測 singalObject == nil即可。
上面的代碼確實是實現了線程安全的單例模式,但是由於load與initialize方法的本意是對於Class做設置,並且考慮到load或initialize方法有可能是在程序運行的早期執行,可能我們的單例所依賴的環境尚未形成,所有我們還是不要用這種方式實現單例吧。
經過測試,當我們的MySpecialObject類中包含其他自定義class的屬性時,如Book。
當我們在MySepcialObject 的init函數中初始化Book屬性時,此時雖然Book類的load方法沒被調用,但是我們的Book屬性仍然能被正常初始化。
也許,對於load方法的調用,Runtime是在將所有的Class加載到內存中後,再統一調用的原因嗎?
關於自己最後一個關於Book屬性的問題,在查閱了Runtime源碼之後,確實如推測那樣,Runtime會在所有類都加載入內存後,在統一的調用load方法。當初在寫這篇博客時,還沒有去深入的分析Runtime的源碼(還沒有能力:)),現在回過頭來看一下,這也就是對一個問題不斷深入理解的過程吧~
附上相關代碼, 當dyld將所有的class image都映射到內存後,會調用load images來加載class,最後,會調用load 方法:
void
load_images(const char *path __unused, const struct mach_header *mh)
{
// Return without taking locks if there are no +load methods here.
if (!hasLoadMethods((const headerType *)mh)) return;
recursive_mutex_locker_t lock(loadMethodLock);
// Discover load methods
{
rwlock_writer_t lock2(runtimeLock);
prepare_load_methods((const headerType *)mh);
}
// Call +load methods (without runtimeLock - re-entrant)
call_load_methods();
}