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

上一篇文章我們分析了dyld跟objc的關(guān)聯(lián)中踩萎,已經(jīng)研究到了_dyld_objc_notify_register中會(huì)調(diào)用到map_images哮肚、load_images登夫,并且對(duì)于map_images也做了一些分析。map_images中會(huì)調(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)非懶加載的類悼嫉,對(duì)于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í)行到這里就打印拼窥。接下來(lái)我們?cè)贚GPerson中實(shí)現(xiàn)+load方法戏蔑,看看打印:

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

接下來(lái)我們把+load方法注釋掉再看看总棵,打印沒(méi)有了,說(shuō)明如果不實(shí)現(xiàn)+load改含,這里面確實(shí)不會(huì)進(jìn)行加載情龄。
這里引出了我們本篇文章分析的一個(gè)話題,懶加載類與非懶加載類捍壤。
懶加載類其實(shí)就是指類的加載在第一次消息發(fā)送之前骤视,但是如果我們?cè)陬愔袑?shí)現(xiàn)了+load方法,那么類的加載就會(huì)提前到pre-main之前,提前加載的類就稱之為非懶加載類。
在上面的源碼中,我們可以找到其中的關(guān)鍵方法realizeClassWithoutSwift辟癌。接下來(lái)我們就繼續(xù)去看看realizeClassWithoutSwift迂曲。

類的加載

realizeClassWithoutSwift我們先看其源碼實(shí)現(xiàn)肉盹,因?yàn)槲覀冞@里主要探究的是加載萨西,其中加載具體做的事情不做過(guò)多說(shuō)明妄壶,把部分源碼進(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ù)方法的注釋我們可以解讀出以下信息:

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

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

懶加載類

我們實(shí)現(xiàn)了+load方法類的加載就會(huì)提前戈毒,+load是如何影響類的加載的時(shí)機(jī)的呢艰猬?
load_images源碼中有說(shuō)明在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: 如果這里沒(méi)有+load方法冠桃,則返回而不帶鎖。
從對(duì)hasLoadMethods注釋我們知道道宅,load_images是通過(guò)hasLoadMethods方法食听,來(lái)判斷是否有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接下來(lái)是調(diào)用了prepare_load_methods來(lái)發(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");
        }
        //如果類沒(méi)有初始化就去初始化
        realizeClassWithoutSwift(cls, nil);
        ASSERT(cls->ISA()->isRealized());
         // 循環(huán)遍歷去加載非懶加載分類的 load 方法到 loadable_categories
        add_category_to_loadable_list(cat);
    }
}

prepare_load_methods中分為兩部分:
1.獲取非懶加載類列表,猜測(cè)這里應(yīng)該已經(jīng)加載了對(duì)應(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。
非懶加載分類遍歷時(shí)陋桂,有一個(gè)處理realizeClassWithoutSwift(cls, nil)逆趣,在遍歷加載非懶加載類的load方法時(shí),會(huì)調(diào)用realizeClassWithoutSwift章喉,如果分類對(duì)應(yīng)的類沒(méi)有記載汗贫,在這里就會(huì)被加載身坐。

懶加載類

對(duì)于懶加載類,是在第一次消息發(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中也會(huì)調(diào)用到realizeClassWithoutSwift落包,對(duì)類進(jìn)行加載部蛇。

總結(jié)

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

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

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市有序,隨后出現(xiàn)的幾起案子抹腿,更是在濱河造成了極大的恐慌,老刑警劉巖旭寿,帶你破解...
    沈念sama閱讀 206,378評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件警绩,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡盅称,警方通過(guò)查閱死者的電腦和手機(jī)肩祥,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)缩膝,“玉大人混狠,你說(shuō)我怎么就攤上這事〖膊悖” “怎么了将饺?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,702評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)痛黎。 經(jīng)常有香客問(wèn)我予弧,道長(zhǎng),這世上最難降的妖魔是什么舅逸? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,259評(píng)論 1 279
  • 正文 為了忘掉前任桌肴,我火速辦了婚禮,結(jié)果婚禮上琉历,老公的妹妹穿的比我還像新娘坠七。我一直安慰自己,他們只是感情好旗笔,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,263評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布彪置。 她就那樣靜靜地躺著,像睡著了一般蝇恶。 火紅的嫁衣襯著肌膚如雪拳魁。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,036評(píng)論 1 285
  • 那天撮弧,我揣著相機(jī)與錄音潘懊,去河邊找鬼姚糊。 笑死,一個(gè)胖子當(dāng)著我的面吹牛授舟,可吹牛的內(nèi)容都是我干的救恨。 我是一名探鬼主播,決...
    沈念sama閱讀 38,349評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼释树,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼肠槽!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起奢啥,我...
    開(kāi)封第一講書(shū)人閱讀 36,979評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤秸仙,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后桩盲,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體寂纪,經(jīng)...
    沈念sama閱讀 43,469評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,938評(píng)論 2 323
  • 正文 我和宋清朗相戀三年赌结,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了弊攘。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,059評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡姑曙,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出迈倍,到底是詐尸還是另有隱情伤靠,我是刑警寧澤,帶...
    沈念sama閱讀 33,703評(píng)論 4 323
  • 正文 年R本政府宣布啼染,位于F島的核電站宴合,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏迹鹅。R本人自食惡果不足惜卦洽,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,257評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望斜棚。 院中可真熱鬧阀蒂,春花似錦、人聲如沸弟蚀。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,262評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)义钉。三九已至昧绣,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間捶闸,已是汗流浹背夜畴。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工拖刃, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人贪绘。 一個(gè)月前我還...
    沈念sama閱讀 45,501評(píng)論 2 354
  • 正文 我出身青樓兑牡,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親兔簇。 傳聞我的和親對(duì)象是個(gè)殘疾皇子发绢,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,792評(píng)論 2 345