Objective - C Category(二)load 方法、initialize方法

(一)load方法

  • 类及其分类都有load方法,+load方法会在runtime加载类、分类时调用
  • 每个类、分类的+load,在程序运行过程中只调用一次
(1)调用顺序
  1. 先调用类的+load
  • 按照编译先后顺序调用(先编译,先调用)
  • 调用子类的+load之前会先调用父类的+load
  1. 再调用分类的+load
  • 按照编译先后顺序调用(先编译,先调用)
(2)如何查看源码证明调用顺序?

源码解读顺序:
(1) objc-os.mm文件

  • _objc_init
  • load_images

(2) objc-runtime-new.mm文件

  • load_images
  • load_images_nolock寻找\整理load方法
  • call_load_methods调用load方法

(3)objc-loadmethod.mm文件

  • call_load_methods方法 中 依次调用call_class_loadscall_category_loads
  • 查看这两个方法 即直接依次获取元类、分类的load方法并加载

重点函数讲解:

void call_load_methods(void)
{
    static bool loading = NO;
    bool more_categories;

    loadMethodLock.assertLocked();

    // 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 
        // 加载元类对象load方法
        while (loadable_classes_used > 0) {
            call_class_loads();
        }

        // 2. Call category +loads ONCE
        //加载categoty的load方法
        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;
}

其中有call_class_loadscall_category_loads,下面只列举call_class_loads

static void call_class_loads(void)
{
    int i;
    
    // Detach current loadable list.
    //这个classes为前面`schedule_class_load`方法通过"整理"出来的数组
    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方法
        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.
    //调用load方法
    if (classes) free(classes);
}
(3)load方法与自定义方法的区别

看到这里也大概有点疑惑,为什么分类的自定义方法会“掩盖”类的原方法,而load不会?

  • +load方法是根据方法地址直接调用,并不是经过objc_msgSend函数调用
  • 自定义方法是经过objc_msgSend函数,进行一个消息查找的过程
(4)不同的类load方法调用顺序

上面只搞清楚了类与分类之间的调用顺序,那么不同的类(可能有继承关系)调用顺序又是什么样的呢?
回到前面的方法load_images,在调用call_load_methods方法前,调用了一个load_images_nolock方法,里面有一个prepare_load_methods方法,我们看一下:

void prepare_load_methods(const headerType *mhdr)
{
    size_t count, i;

    runtimeLock.assertWriting();

    //通过编译顺序,获取的类数组
    classref_t *classlist = 
        _getObjc2NonlazyClassList(mhdr, &count);
    for (i = 0; i < count; i++) {
        //定制规划一些
        schedule_class_load(remapClass(classlist[i]));
    }

    //分类调用顺序 类的load调用完毕再调用分类,先编译先调用
    category_t **categorylist = _getObjc2NonlazyCategoryList(mhdr, &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);
    }
}

我们再看其中的schedule_class_load方法

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
    //递归调用(优先寻找其父类的load方法)
    schedule_class_load(cls->superclass);
    
    //将该类添加到loadable_list中
    add_class_to_loadable_list(cls);
    cls->setInfo(RW_LOADED); 
}

这个loadable_list是在后面call_class_loads调用时用到的,即可说明:

  • 不同的类之间的load方法调用,按编译顺序先后
  • 即使子类最初的编译顺序在前,也会优先调用父类的load方法
    (默认按照编译顺序,但是要将父类优先load)
  • 分类是按照编译顺序依次调用

现在我们也能回答前面的面试题了:

4. Category中有load方法吗?load方法是什么时候调用的?load 方法能继承吗?

答:有load方法;load方法是在runtime加载类、分类的时候调用一次;load方法可以继承的,但是load方法一般是系统在调用,一般不会主动调用

(二)initialize方法

  • +initialize方法会在类第一次接收到消息时调用(第一次alloc)
(1)调用顺序
  1. 先调用父类的+initialize,再调用子类的+initialize
  • 先初始化父类,再初始化子类,每个类只会初始化1次
(1)如何查看源码证明调用顺序?

源码解读顺序:
(1) objc-msg-arm64.s文件

  • objc_msgSend

(2) objc-runtime-new.mm文件

  • class_getInstanceMethod
  • lookUpImpOrNil
  • lookUpImpOrForward
  • _class_initialize
  • callInitialize
  • objc_msgSend(cls, SEL_initialize)
(2)+initialize和+load的区别
  • +initialize是通过objc_msgSend进行调用的,所以有以下特点
  • 如果子类没有实现+initialize,会调用父类的+initialize(所以父类的+initialize可能会被调用多次,但不代表被初始化多次,只是objc_msgSend调用了父类的initialize方法)
  • 如果分类实现了+initialize,就覆盖类本身的+initialize调用

类方法调用:(还是会调用class_getInstanceMethod)

Method class_getClassMethod(Class cls, SEL sel)
{
    if (!cls  ||  !sel) return nil;

    return class_getInstanceMethod(cls->getMeta(), sel);//将自身传进去
}
IMP lookUpImpOrForward(Class cls, SEL sel, id inst, 
                       bool initialize, bool cache, bool resolver)
{
    Class curClass;
    IMP imp = nil;
    Method meth;
    bool triedResolver = NO;

    runtimeLock.assertUnlocked();

    // Optimistic cache lookup
    if (cache) {
        imp = cache_getImp(cls, sel);
        if (imp) return imp;
    }

    if (!cls->isRealized()) {
        rwlock_writer_t lock(runtimeLock);
        realizeClass(cls);
    }

    //判断是否需要初始化,且还没有被初始化过
    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
    }

    ....//代码省略

    return imp;
}

我们再看_class_initialize方法:

void _class_initialize(Class cls)
{
    assert(!cls->isMetaClass());

    Class supercls;
    BOOL reallyInitialize = NO;

    //重点: initialize之前需要保证父类已initialized 
    // 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_locker_t lock(classInitLock);
        if (!cls->isInitialized() && !cls->isInitializing()) {
            cls->setInitializing();
            reallyInitialize = YES;
        }
    }
    
    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());
        }
        
        //重点:通过objc_msgSend方法调用initialize方法
        ((void(*)(Class, SEL))objc_msgSend)(cls, SEL_initialize);

        if (PrintInitializing) {
            _objc_inform("INITIALIZE: finished +[%s initialize]",
                         cls->nameForLogging());
        }        
        
        // Done initializing. 
        // If the superclass is also done initializing, then update 
        //   the info bits and notify waiting threads.
        // If not, update them later. (This can happen if this +initialize 
        //   was itself triggered from inside a superclass +initialize.)
        monitor_locker_t lock(classInitLock);
        if (!supercls  ||  supercls->isInitialized()) {
            _finishInitializing(cls, supercls);
        } else {
            _finishInitializingAfter(cls, supercls);
        }
        return;
    }

   ...//代码省略
}

因此,通过该方法可以看出, 此处是会先调用父类的initalize,再调用子类的initalize

load和initialize方法的区别是什么?

  1. 调用方式
  • load根据函数地址直接调用
  • initialize是通过objc_msgSend调用
    2.调用时刻
  • load是runtime加载类、分类的时候调用
  • initialize是类第一次接受到消息时调用,且每个类只会被初始化一次(父类的initialize可能会调用多次,子类可能并未实现initialize方法)
    3.调用顺序
  • load方法,①先调用类的load,先编译的类优先调用load,调用子类的load之前会调用父类的load ②其次调用分类的load,先编译优先调用
  • initialize 先初始化父类,再初始化子类(子类可能因为未实现initialize,通过objc_msgSend调用父类方法)

ps: 上一期的遗留,同时思考load与initalize方法调用顺序,即原理

你可能感兴趣的:(Objective - C Category(二)load 方法、initialize方法)