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等博客。

相關文章
相關標籤/搜索