Objective-C 源码(二)+load 以及 +initialize

    +loadapp

    关于+load方法是当类或者分类被添加到Objective-C runtime的时候被调用的,实现该方法可让咱们在类加载的时候,执行一些类相关的行为。子类的+load方法会在它的全部父类的+load方法以后执行,而分类的+load方法会在它的主类的+load方法以后执行。可是不一样类之间的+load方法的调用顺序是不肯定的。函数

    打开 objc-runtime-new.mm 中的 void prepare_load_methods(header_info *hi) 函数:this

    其中 schedule_class_load()对传入参数的父类进行了递归调用,以确保父类优先的顺序。函数执行完以后,当前全部知足+load的方法调用条件的类和分类就被分别存放在全局变量了。atom

    接下来在 objc-loadmethod.m 对方法进行调用,找到其中的 void call_load_methods(void) 函数。设计

    这个函数的做用就是真正负责调用类的+load方法了。它从全局变量 loadable_classes 中取出全部的可供调用的类,并进行清零操做。code

    其中:loadable_classes 指向用于保存类信息的内存的首地址, loadable_classes_allocated 标志已分配的内存空间大小, loadable_classes_used 则标志已使用的内存空间大小。继承

    而后,循环调用全部类的+load方法。这个是亮点:这里+load 直接使用函数内存地址的方式 (*load_method)(cls, SEL_load); 对+load方法进行调用的,而不是使用发送消息 objc_msgSend 的方式!递归

    这样的调用方式就使得+load方法拥有了一个很是有趣的特性,那就是子类,父类和分类中的+load方法是被区别对待的,也就是说,若是子类别没有实现+load方法,那么当它被加载的时候runtime是不会去调用父类的+load方法的。同理,当一个类和它的分类都实现了+load方法的话,两个方法都会被调用。所以,在不少博客上看到的那个viewWillAppear()换成XXX_viewWillAppear()交换方法就是在这个时候被替换掉的。http://nshipster.com/method-swizzling/ ip

    +initizlize
内存

    +initialize 方法是在类或它的子类收到第一条消息以前被调用的,这里所指的消息包括实例方法和类方法的调用。也就是说 +initialize 方法是以懒加载的方式被调用的,若是程序一直没有给某个类或它的子类发送消息,那么这个类的 +initialize 方法是永远不会被调用的。那这样设计有什么好处呢?好处是显而易见的,那就是节省系统资源,避免浪费。

    打开文件 objc-runtime-new.mm:

IMP lookUpImpOrForward(Class cls, SEL sel, id inst, 
                       bool initialize, bool cache, bool resolver)
{
    Class curClass;
    IMP imp = nil;
    Method meth;
    bool triedResolver = NO;

    rwlock_assert_unlocked(&runtimeLock);

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

    if (!cls->isRealized()) {
        rwlock_write(&runtimeLock);
        realizeClass(cls);
        rwlock_unlock_write(&runtimeLock);
    }

    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
    }

    // The lock is held to make method-lookup + cache-fill atomic 
    // with respect to method addition. Otherwise, a category could 
    // be added but ignored indefinitely because the cache was re-filled 
    // with the old value after the cache flush on behalf of the category.
 retry:
    rwlock_read(&runtimeLock);

    // Ignore GC selectors
    if (ignoreSelector(sel)) {
        imp = _objc_ignored_method;
        cache_fill(cls, sel, imp);
        goto done;
    }

    // Try this class's cache.

    imp = cache_getImp(cls, sel);
    if (imp) goto done;

    // Try this class's method lists.

    meth = getMethodNoSuper_nolock(cls, sel);
    if (meth) {
        log_and_fill_cache(cls, cls, meth->imp, sel);
        imp = meth->imp;
        goto done;
    }

    // Try superclass caches and method lists.

    curClass = cls;
    while ((curClass = curClass->superclass)) {
        // Superclass cache.
        imp = cache_getImp(curClass, sel);
        if (imp) {
            if (imp != (IMP)_objc_msgForward_impcache) {
                // Found the method in a superclass. Cache it in this class.
                log_and_fill_cache(cls, curClass, imp, sel);
                goto done;
            }
            else {
                // Found a forward:: entry in a superclass.
                // Stop searching, but don't cache yet; call method 
                // resolver for this class first.
                break;
            }
        }

        // Superclass method list.
        meth = getMethodNoSuper_nolock(curClass, sel);
        if (meth) {
            log_and_fill_cache(cls, curClass, meth->imp, sel);
            imp = meth->imp;
            goto done;
        }
    }

    // No implementation found. Try method resolver once.

    if (resolver  &&  !triedResolver) {
        rwlock_unlock_read(&runtimeLock);
        _class_resolveMethod(cls, sel, inst);
        // Don't cache the result; we don't hold the lock so it may have 
        // changed already. Re-do the search from scratch instead.
        triedResolver = YES;
        goto retry;
    }

    // No implementation found, and method resolver didn't help. 
    // Use forwarding.

    imp = (IMP)_objc_msgForward_impcache;
    cache_fill(cls, sel, imp);

 done:
    rwlock_unlock_read(&runtimeLock);

    // paranoia: look for ignored selectors with non-ignored implementations
    assert(!(ignoreSelector(sel)  &&  imp != (IMP)&_objc_ignored_method));

    // paranoia: never let uncached leak out
    assert(imp != _objc_msgSend_uncached_impcache);

    return imp;
}

当咱们给某个类发送消息时,runtime 会调用这个函数在类中查找相应方法的实现或进行消息转发,当类没有初始化时 runtime 会调用 void _class_initialize(Class cls) 函数对该类进行初始化。点进去 _class_initialize方法查看,能够看到一句: ((void(*)(Class, SEL))objc_msgSend)(cls, SEL_initialize); 说明+initialize 是调用消息发送的机制objc_msgSend进行实现的。也就是说 +initialize 方法的调用与普通方法的调用是同样的,走的都是发送消息的流程。换言之,若是子类没有实现 +initialize 方法,那么继承自父类的实现会被调用;若是一个类的分类实现了 +initialize 方法,那么就会对这个类中的实现形成覆盖。

    所以,若是一个子类没有实现 +initialize 方法,那么父类的实现是会被执行屡次的。有时候,这多是你想要的;但若是咱们想确保本身的 +initialize 方法只执行一次,避免屡次执行可能带来的反作用时,咱们可使用下面的代码来实现:

+ (void)initialize {
  if (self == [ClassName self]) {
    // ... do the initialization ...
  }
}

或者使用:

+ (void)initialize {
    static BOOL b = false;
    if (!b) {
        NSLog(@"Person initialize");
        b = true;
    }
}

    补充:

    +load是在runtime以前就被调用的,+initialize是在runtime才调用。

    若是父类和子类的+initialize方法都被调用,父类的调用必定在子类以前,这是系统自动完成的,子类+initialize中不必显式调用[super initialize];

    某个类的+initialize的方法不必定只被调用一次,至少有两种状况会被调用屡次:

        子类显式调用[super initialize];;

        子类没有实现+initialize方法;

    ——参考了leichunfeng zhangbuhuai yulingtianxia sunny nshipster等博客。

相关文章
相关标签/搜索