通過上一篇的介紹我們知道了App
的啓動流程中dyld
做了哪些事情,那麼:
+load
和+initialize
分別是什麼時候調用的呢?以及他們在父類,子類,分類中調用的順序是什麼樣的呢?
我們先準備如下幾個Class
和Category
:
Class-NXPerson
代碼:
@interface NXPerson : NSObject
@property (nonatomic, copy) NSString *name;
- (void)run;
@end
@implementation NXPerson
+ (void)load {
NSLog(@"%s",__func__);
}
+ (void)initialize {
NSLog(@"%s",__func__);
}
- (void)run{
NSLog(@"%s",__func__);
}
@end
Class-NXTeacher
代碼:
@interface NXTeacher : NXPerson
- (void)teach;
@end
@implementation NXTeacher
+ (void)load {
NSLog(@"%s",__func__);
}
+ (void)initialize {
NSLog(@"%s",__func__);
}
- (void)teach{
NSLog(@"%s",__func__);
}
@end
Class-NXStudent
代碼:
@interface NXStudent : NXPerson
- (void)study;
@end
@implementation NXStudent
+ (void)load {
NSLog(@"%s",__func__);
}
+ (void)initialize {
NSLog(@"%s",__func__);
}
- (void)study{
NSLog(@"%s",__func__);
}
@end
Category-NXPerson(Category)
代碼:
@interface NXPerson(Category)
@end
@implementation NXPerson(Category)
+ (void)load {
NSLog(@"%s",__func__);
}
+ (void)initialize {
NSLog(@"%s",__func__);
}
- (void)run {
NSLog(@"%s",__func__);
}
@end
Category-NXTeacher(Category)
代碼:
@interface NXTeacher(Category)
@end
@implementation NXTeacher(Category)
+ (void)load {
NSLog(@"%s",__func__);
}
+ (void)initialize {
NSLog(@"%s",__func__);
}
- (void)teach {
NSLog(@"%s",__func__);
}
@end
Category-NXStudent(Category)
代碼:
@interface NXStudent(Category)
@end
@implementation NXStudent(Category)
+ (void)load {
NSLog(@"%s",__func__);
}
+ (void)initialize {
NSLog(@"%s",__func__);
}
- (void)study {
NSLog(@"%s",__func__);
}
@end
代碼如上,一共三個類
NXPerson
、NXTeacher
、NXStudent
,後2者繼承於前者;三個分類NXPerson(Category)
、NXTeacher(Category)
、NXStudent(Category)
。每個主類分別實現了-run
、-teach
、-study
和+load
、+initialize
方法。每個分類中也實現了主類的方法和+load
、+initialize
方法。
1.+load
方法
首先我們在沒有任何調用的情況下的運行代碼:
探索:
1.Class
和Category
的+load
方法都被調用了,+initialize
方法沒有被調用。
2.Class
是不是一定在Category
前面打印呢?我們將NXPerson(Category)
放在NXStudent
和NXTeacher
之前,繼續運行代碼發現Class
的+load
依舊比Category
的+load
更先調用。
3.我們更換幾個Class
的順序,發現NXPerson
的+load
永遠在最前,同級子類加載的順序跟代碼的順序有關。至此我們可以推斷父類的+load
先於子類調用。
4.我們更換幾個Category
的順序,發現誰的代碼在前誰先打印,即分類的+load
方法加載的順序是根據代碼讀取的順序來決定的。
以上結論其實是可以在objc源碼中找到答案的:在call_load_methods
中我們也可以看到call_class_loads
先於call_category_loads
調用(源碼如下),這個決定了所有的父類的+load
更先讀取。
void call_load_methods(void){
...
//調用loadable_classes中`loadable_class`綁定的`load`方法
call_class_loads();
//調用loadable_categories中`loadable_category`綁定的`load`方法
call_category_loads();
....
}
2.initialize方法
我們添加如下代碼到main
中:
NXTeacher *p = [[NXTeacher alloc] init];
打印結果如下:
[NXPerson(Category) initialize]
[NXTeacher(Category) initialize]
反覆運行打印的只有分類的+initialize
,以及分類對應類的父類的分類的+initialize
,並且父類先於之類調用.
探索:
1.+initialize
方法默認會在main之後,第一次訪問該類的時候調用。通常首次訪問的方法+alloc
、+self
、+class
、+new
等等類方法。
2.initialize
會先調用父類,再調用子類。如果同級有分類,則分類的方法會覆蓋掉主類的方法,多個分類的會調用最後一個加載進內存的分類的方法。
上面我們講到最先訪問到一個類的方式通常是+alloc
、+self
、+class
、+new
方法,接下來我們就初始化一個[NXTeacher alloc]
,而Objective-C
的方法調用,底層都是通過消息轉發objc_msgSend
實現的,那麼[NXTeacher alloc]
會被轉化爲objc_msgSend(NXTeacher, @selector(alloc))
來進入彙編代碼實現的快速查找流程從緩存中查找,那麼第一次調用緩存中肯定沒有alloc
方法的;緩存中沒有找到,則會進入慢速查找流程lookUpImpOrForward
:
NEVER_INLINE IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior){
...
cls = realizeAndInitializeIfNeeded_locked(inst, cls, behavior & LOOKUP_INITIALIZE);
...
}
static Class realizeAndInitializeIfNeeded_locked(id inst, Class cls, bool initialize){
if (slowpath(!cls->isRealized())) {
/*實現該類*/
cls = realizeClassMaybeSwiftAndLeaveLocked(cls, runtimeLock);
}
if (slowpath(initialize && !cls->isInitialized())) {
/*初始化該類*/
cls = initializeAndLeaveLocked(cls, inst, runtimeLock);
}
return cls;
}
2.1.分類方法是怎麼添加的?
static Class realizeClassMaybeSwiftAndLeaveLocked(Class cls, mutex_t& lock) {
return realizeClassMaybeSwiftMaybeRelock(cls, lock, true);
}
static Class realizeClassMaybeSwiftMaybeRelock(Class cls, mutex_t& lock, bool leaveLocked){
...
if (!cls->isSwiftStable_ButAllowLegacyForNow()) {
realizeClassWithoutSwift(cls, nil);
}
else {
cls = realizeSwiftClass(cls);
}
return cls;
}
static Class realizeClassWithoutSwift(Class cls, Class previously){
...
supercls = realizeClassWithoutSwift(remapClass(cls->getSuperclass()), nil);
metacls = realizeClassWithoutSwift(remapClass(cls->ISA()), nil);
...
// Update superclass and metaclass in case of remapping
cls->setSuperclass(supercls);
cls->initClassIsa(metacls);
...
// Attach categories
methodizeClass(cls, previously);
return cls;
}
static void methodizeClass(Class cls, Class previously){
...
// Attach categories
if (previously) {
if (isMeta) {
//類方法添加到元類上
objc::unattachedCategories.attachToClass(cls, previously,
ATTACH_METACLASS);
} else {
//實例方法添加到類上
objc::unattachedCategories.attachToClass(cls, previously,
ATTACH_CLASS_AND_METACLASS);
}
}
objc::unattachedCategories.attachToClass(cls, cls,
isMeta ? ATTACH_METACLASS : ATTACH_CLASS);
}
void attachToClass(Class cls, Class previously, int flags){
...
auto &map = get();
auto it = map.find(previously);
if (it != map.end()) {
category_list &list = it->second;
if (flags & ATTACH_CLASS_AND_METACLASS) {
int otherFlags = flags & ~ATTACH_CLASS_AND_METACLASS;
attachCategories(cls, list.array(), list.count(), otherFlags | ATTACH_CLASS);
attachCategories(cls->ISA(), list.array(), list.count(), otherFlags | ATTACH_METACLASS);
}
else {
attachCategories(cls, list.array(), list.count(), flags);
}
map.erase(it);
}
}
static void attachCategories(Class cls, const locstamped_category_t *cats_list, uint32_t cats_count,
int flags){
...
for (uint32_t i = 0; i < cats_count; i++) {
auto& entry = cats_list[i];
method_list_t *mlist = entry.cat->methodsForMeta(isMeta);
if (mlist) {
if (mcount == ATTACH_BUFSIZ) {
prepareMethodLists(cls, mlists, mcount, NO, fromBundle, __func__);
rwe->methods.attachLists(mlists, mcount);
mcount = 0;
}
mlists[ATTACH_BUFSIZ - ++mcount] = mlist;
fromBundle |= entry.hi->isBundle();
}
...//property_list_t, protocol_list_t
}
if (mcount > 0) {
prepareMethodLists(cls, mlists + ATTACH_BUFSIZ - mcount, mcount,
NO, fromBundle, __func__);
rwe->methods.attachLists(mlists + ATTACH_BUFSIZ - mcount, mcount);
...
}
...
}
//這個方法裏邊在插入的時候
void attachLists(List* const * addedLists, uint32_t addedCount) {
...
// many lists -> many lists
uint32_t oldCount = array()->count;
uint32_t newCount = oldCount + addedCount;
array_t *newArray = (array_t *)malloc(array_t::byteSize(newCount));
newArray->count = newCount;
array()->count = newCount;
/*第1個for循環相當於將老數據往後平移了addedCount個位置*/
for (int i = oldCount - 1; i >= 0; i--)
newArray->lists[i + addedCount] = array()->lists[I];
/*第2個for循環相當於將新數據按順序放在前面*/
for (unsigned i = 0; i < addedCount; i++)
newArray->lists[i] = addedLists[I];
free(array());
setArray(newArray);
validate();
...
}
如此以來按順序查找的時候,那麼排在前面的category
方法會被優先調用,categoey-initialze
會被調用,而主類沒有被調用的真正原因。
2.2.initialize
調用順序
static Class initializeAndLeaveLocked(Class cls, id obj, mutex_t& lock{
return initializeAndMaybeRelock(cls, obj, lock, true);
}
static Class initializeAndMaybeRelock(Class cls, id inst, mutex_t& lock, bool leaveLocked){
...
initializeNonMetaClass(nonmeta);
...
}
void initializeNonMetaClass(Class cls){
...
/*重點!!!,這裏會先調用父類的initialize方法*/
supercls = cls->getSuperclass();
if (supercls && !supercls->isInitialized()) {
initializeNonMetaClass(supercls);
}
allInitialize(cls);
}
void callInitialize(Class cls){
((void(*)(Class, SEL))objc_msgSend)(cls, @selector(initialize));
}
通過以上的流程,我們可以看出initialize
方法的調用時先調用父類,再調用當前類的。
總結:
一、關於load
方法:
1.class
和category
的+load
方法都會被調用,是在main
之前被調用。
2.調用順序先主類的+load
,再調用分類的+load
。主類的調用順序爲先superclass
,再subclass
。分類的+load
是根據代碼讀取的順序。子類的+load
可能會在前面。
二、關於initialize
方法:
1.如果有分類,那麼分類的方法列表會加載進主類的方法列表中,並且放在方法列表的頭部,按照代碼的讀取順序,最後讀取到分類方法會放在最前面。按照順序查找則最後加載進去的方法會被優先查找到(這也就是主類的同名方法會被分類【覆蓋掉】的原因)。分類initialize
會被調用,而主類的initialize
不會被調用。
2.調用順序依然是先superclass
,再subclass
。