iOS底層探索:load、initialize方法的調用規則

通過上一篇的介紹我們知道了App的啓動流程中dyld做了哪些事情,那麼:

+load+initialize分別是什麼時候調用的呢?以及他們在父類,子類,分類中調用的順序是什麼樣的呢?

我們先準備如下幾個ClassCategory

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

代碼如上,一共三個類NXPersonNXTeacherNXStudent,後2者繼承於前者;三個分類NXPerson(Category)NXTeacher(Category)NXStudent(Category)。每個主類分別實現了-run-teach-study+load+initialize方法。每個分類中也實現了主類的方法和+load+initialize方法。

1.+load方法

首先我們在沒有任何調用的情況下的運行代碼:


探索:
1.ClassCategory+load方法都被調用了,+initialize方法沒有被調用。
2.Class是不是一定在Category前面打印呢?我們將NXPerson(Category)放在NXStudentNXTeacher之前,繼續運行代碼發現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.classcategory+load方法都會被調用,是在main之前被調用。
2.調用順序先主類的+load,再調用分類的+load。主類的調用順序爲先superclass,再subclass。分類的+load是根據代碼讀取的順序。子類的+load可能會在前面。
二、關於initialize方法:
1.如果有分類,那麼分類的方法列表會加載進主類的方法列表中,並且放在方法列表的頭部,按照代碼的讀取順序,最後讀取到分類方法會放在最前面。按照順序查找則最後加載進去的方法會被優先查找到(這也就是主類的同名方法會被分類【覆蓋掉】的原因)。分類initialize會被調用,而主類的initialize不會被調用。
2.調用順序依然是先superclass,再subclass

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章