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

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