底層原理:懶加載類與非懶加載類

上一篇文章我們分析了dyld跟objc的關(guān)聯(lián)中,已經(jīng)研究到了_dyld_objc_notify_register中會調(diào)用到map_images、load_images,并且對于map_images也做了一些分析。map_images中會調(diào)用map_images_nolock然后調(diào)用_read_images,_read_images源碼中有這么一段:

    // 實(shí)現(xiàn)非懶加載(+load方法及靜態(tài)實(shí)例)
    for (EACH_HEADER) {
        classref_t const *classlist = 
            _getObjc2NonlazyClassList(hi, &count);
        for (i = 0; i < count; i++) {
            Class cls = remapClass(classlist[i]);
            if (!cls) continue;

            const char *mangledName = cls->mangledName();
            const char *clsName = "LGPerson";
            if (strcmp(mangledName, clsName)==0) {
                printf("%s實(shí)現(xiàn)非懶加載的類,對于load方法和靜態(tài)實(shí)例變量       -%s",__func__,mangledName);
            }

            addClassTableEntry(cls);

            if (cls->isSwiftStable()) {
                if (cls->swiftMetadataInitializer()) {
                    _objc_fatal("Swift class %s with a metadata initializer "
                                "is not allowed to be non-lazy",
                                cls->nameForLogging());
                }
                // fixme also disallow relocatable classes
                // We can't disallow all Swift classes because of
                // classes like Swift.__EmptyArrayStorage
            }
            realizeClassWithoutSwift(cls, nil);
        }
    }

@implementation LGPerson

+ (void)load {
    NSLog(@"LGPerson load");
}

@end

在里面我們加了一段打印,判斷如果是我們自己定義的類執(zhí)行到這里就打印。接下來我們在LGPerson中實(shí)現(xiàn)+load方法,看看打?。?/p>

_read_images實(shí)現(xiàn)非懶加載的類,對于load方法和靜態(tài)實(shí)例變量-LGPerson
2020-10-21 08:32:48.113095+0800 KCObjc[50894:1139354] LGPerson load

接下來我們把+load方法注釋掉再看看,打印沒有了,說明如果不實(shí)現(xiàn)+load,這里面確實(shí)不會進(jìn)行加載。
這里引出了我們本篇文章分析的一個話題,懶加載類與非懶加載類。
懶加載類其實(shí)就是指類的加載在第一次消息發(fā)送之前,但是如果我們在類中實(shí)現(xiàn)了+load方法,那么類的加載就會提前到pre-main之前,提前加載的類就稱之為非懶加載類。
在上面的源碼中,我們可以找到其中的關(guān)鍵方法realizeClassWithoutSwift。接下來我們就繼續(xù)去看看realizeClassWithoutSwift。

類的加載

realizeClassWithoutSwift我們先看其源碼實(shí)現(xiàn),因?yàn)槲覀冞@里主要探究的是加載,其中加載具體做的事情不做過多說明,把部分源碼進(jìn)行了省略。

static Class realizeClassWithoutSwift(Class cls, Class previously)
{
    runtimeLock.assertLocked();
    class_rw_t *rw;
    Class supercls;
    Class metacls;
    if (!cls) return nil;
    if (cls->isRealized()) return cls;
    ASSERT(cls == remapClass(cls));
    // fixme verify class is not in an un-dlopened part of the shared cache?
    auto ro = (const class_ro_t *)cls->data();
    auto isMeta = ro->flags & RO_META;
    if (ro->flags & RO_FUTURE) {
        // This was a future class. rw data is already allocated.
        rw = cls->data();
        ro = cls->data()->ro();
        ASSERT(!isMeta);
        cls->changeInfo(RW_REALIZED|RW_REALIZING, RW_FUTURE);
    } else {
        // Normal class. Allocate writeable class data.
        rw = objc::zalloc<class_rw_t>();
        rw->set_ro(ro);
        rw->flags = RW_REALIZED|RW_REALIZING|isMeta;
        cls->setData(rw);
    }
   ...
    // Realize superclass and metaclass, if they aren't already.
    // This needs to be done after RW_REALIZED is set above, for root classes.
    // This needs to be done after class index is chosen, for root metaclasses.
    // This assumes that none of those classes have Swift contents,
    //   or that Swift's initializers have already been called.
    //   fixme that assumption will be wrong if we add support
    //   for ObjC subclasses of Swift classes.
    supercls = realizeClassWithoutSwift(remapClass(cls->superclass), nil);
    metacls = realizeClassWithoutSwift(remapClass(cls->ISA()), nil);
    ...
    // Update superclass and metaclass in case of remapping
    cls->superclass = supercls;
    cls->initClassIsa(metacls);
   ...
   return cls;
}

根據(jù)方法的注釋我們可以解讀出以下信息:

  • 對類cls執(zhí)行首次初始化
  • 包括分配讀寫數(shù)據(jù)。
  • 不執(zhí)行任何Swift側(cè)初始化。
  • 返回類的實(shí)際類結(jié)構(gòu)。
  • 鎖定:runtimeLock必須由調(diào)用者寫鎖

對方法的實(shí)現(xiàn)的一些概念進(jìn)行解讀
ro:干凈內(nèi)存(Clean Memory),存放的是類的原始數(shù)據(jù)
rw:臟內(nèi)存(Dirty Memory) ,運(yùn)行時會對類內(nèi)存進(jìn)行動態(tài)的修改所以才有rw,rw最初是從ro中讀取的數(shù)據(jù)。
rwe:新增內(nèi)容,運(yùn)行時動態(tài)修改類才會生成rwe,rwe的原始數(shù)據(jù)是從rw中讀取的。
supercls = realizeClassWithoutSwift(remapClass(cls->superclass), nil);沿著繼承鏈遞歸調(diào)用realizeClassWithoutSwift。
metacls = realizeClassWithoutSwift(remapClass(cls->ISA()), nil);沿著isa走位遞歸調(diào)用realizeClassWithoutSwift。
所以如果一個類加載了,其繼承鏈上的父類、isa對應(yīng)的元類等都會加載。

懶加載類

我們實(shí)現(xiàn)了+load方法類的加載就會提前,+load是如何影響類的加載的時機(jī)的呢?
load_images源碼中有說明在dyld映射的鏡像中處理+load,我們需要去看看load_images中是如何處理+load方法的。

void
load_images(const char *path __unused, const struct mach_header *mh)
{
    if (!didInitialAttachCategories && didCallDyldNotifyRegister) {
        didInitialAttachCategories = true;
        loadAllCategories();
    }

    // Return without taking locks if there are no +load methods here.
    if (!hasLoadMethods((const headerType *)mh)) return;

    recursive_mutex_locker_t lock(loadMethodLock);

    // Discover load methods
    {
        mutex_locker_t lock2(runtimeLock);
        prepare_load_methods((const headerType *)mh);
    }

    // Call +load methods (without runtimeLock - re-entrant)
    call_load_methods();
}

hasLoadMethods: 如果這里沒有+load方法,則返回而不帶鎖。
從對hasLoadMethods注釋我們知道,load_images是通過hasLoadMethods方法,來判斷是否有Load方法。

bool hasLoadMethods(const headerType *mhdr)
{
    size_t count;
    if (_getObjc2NonlazyClassList(mhdr, &count)  &&  count > 0) return true;
    if (_getObjc2NonlazyCategoryList(mhdr, &count)  &&  count > 0) return true;
    return false;
}

hasLoadMethods中的處理:

  • _getObjc2NonlazyClassList:獲取所有類中的Load方法數(shù)量
  • _getObjc2NonlazyCategoryList:獲取所有分類中的Load方法數(shù)量

load_images接下來是調(diào)用了prepare_load_methods來發(fā)現(xiàn)所有的load方法,并且這里是加了鎖的。

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

    runtimeLock.assertLocked();

    //獲取非懶加載類
    classref_t const *classlist = 
        _getObjc2NonlazyClassList(mhdr, &count);
    for (i = 0; i < count; i++) {
        //循環(huán)便利加載非懶加載類的load方法到loadable_classes
        schedule_class_load(remapClass(classlist[i]));
    }

    //獲取非懶加載分類列表
    category_t * const *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
        if (cls->isSwiftStable()) {
            _objc_fatal("Swift class extensions and categories on Swift "
                        "classes are not allowed to have +load methods");
        }
        //如果類沒有初始化就去初始化
        realizeClassWithoutSwift(cls, nil);
        ASSERT(cls->ISA()->isRealized());
         // 循環(huán)遍歷去加載非懶加載分類的 load 方法到 loadable_categories
        add_category_to_loadable_list(cat);
    }
}

prepare_load_methods中分為兩部分:
1.獲取非懶加載類列表,猜測這里應(yīng)該已經(jīng)加載了對應(yīng)的類,循環(huán)遍歷加載非懶加載類的load方法到loadable_classes.其中關(guān)鍵的方法schedule_class_load、add_class_to_loadable_list。
2.獲取非懶加載分類列表,循環(huán)遍歷去加載非懶加載分類的 load 方法到 loadable_categories。
其中關(guān)鍵的方法add_category_to_loadable_list。
非懶加載分類遍歷時,有一個處理realizeClassWithoutSwift(cls, nil),在遍歷加載非懶加載類的load方法時,會調(diào)用realizeClassWithoutSwift,如果分類對應(yīng)的類沒有記載,在這里就會被加載。

懶加載類

對于懶加載類,是在第一次消息發(fā)送objc_msgSend,調(diào)用到lookUpImpOrForward

IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior)
{
    if (slowpath(!cls->isRealized())) {
        cls = realizeClassMaybeSwiftAndLeaveLocked(cls, runtimeLock);
        // runtimeLock may have been dropped but is now locked again
    }
    return imp;
}
static Class
realizeClassMaybeSwiftAndLeaveLocked(Class cls, mutex_t& lock)
{
    return realizeClassMaybeSwiftMaybeRelock(cls, lock, true);
}
static Class
realizeClassMaybeSwiftMaybeRelock(Class cls, mutex_t& lock, bool leaveLocked)
{
    lock.assertLocked();

    if (!cls->isSwiftStable_ButAllowLegacyForNow()) {
        // Non-Swift class. Realize it now with the lock still held.
        // fixme wrong in the future for objc subclasses of swift classes
        realizeClassWithoutSwift(cls, nil);
        if (!leaveLocked) lock.unlock();
    } else {
        // Swift class. We need to drop locks and call the Swift
        // runtime to initialize it.
        lock.unlock();
        cls = realizeSwiftClass(cls);
        ASSERT(cls->isRealized());    // callback must have provoked realization
        if (leaveLocked) lock.lock();
    }

    return cls;
}

我們可以清晰的看到lookUpImpOrForward中也會調(diào)用到realizeClassWithoutSwift,對類進(jìn)行加載。

總結(jié)

懶加載類情況 類加載延遲到第一次消息發(fā)送。
lookUpImOrForward
realizeClassMaybeSwiftMaybeRelock
relizeClassWithoutSwift
methodizeClass

非懶記載類調(diào)用了+load方法,類就會提前加載。
getObjc2NonlazyClassList
readClass
realizeClassWithoutSwift
methodizeClass

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容