iOS類的加載

一沽翔,應(yīng)用程序加載回顧

通過前面的學(xué)習(xí)我們對iOS應(yīng)用程序的加載有了一個大致的認(rèn)識部翘,

  • 1 系統(tǒng)調(diào)用exec() 會讓我們的應(yīng)用程序映射到信的地址空間
  • 2 然后通過dyld 進(jìn)行加載砾医、鏈接亡脸、初始化主程序和應(yīng)用程序所依賴的各種動態(tài)庫
  • 3 最后在initializeMainExecutable方法中進(jìn)過一系列的初始化調(diào)用notifySingle 函數(shù)校镐,該函數(shù)會執(zhí)行一個load_images的回調(diào)
  • 4 然后在doModInitFunctions 函數(shù)內(nèi)部調(diào)用__attribute__((contrustor))的c++ 函數(shù)
  • 5 然后dyld返回主程序的入口函數(shù)亿扁,開始進(jìn)入主程序的main函數(shù)。

main函數(shù)執(zhí)行過程中鸟廓,其實dyld還會在流程中初始化libSystem,而libSystem又會去初始化libDispatch,在libDispatch初始化方法里面又會調(diào)用os_object_int,在os_object_int內(nèi)部就會調(diào)用objc_init,從而才會進(jìn)入我們相隔的類的加載過程从祝。這就是整個應(yīng)用程序加載的大致過程。

二引谜,類的加載

通過上一章節(jié)應(yīng)用程序加載回顧的流程可知牍陌,我們知道相關(guān)的加載,編譯在main函數(shù)之前员咽,所以在此我們進(jìn)入libObjc開源的代碼中全局搜索read_image字段呐赡。、進(jìn)入到相關(guān)的定義如下 :摘取片段展示

void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int unoptimizedTotalClasses)
{
    header_info *hi;
    uint32_t hIndex;
    size_t count;
    size_t i;
    Class *resolvedFutureClasses = nil;
    size_t resolvedFutureClassCount = 0;
    static bool doneOnce;
    bool launchTime = NO;
    TimeLogger ts(PrintImageTimes);
骏融。链嘀。。档玻。怀泊。。误趴。霹琼。。。枣申。

既然本文重點是研究類的加載售葡,那么我們就在read_images中找到關(guān)于類的信息,從而重點的進(jìn)行研究和學(xué)習(xí)忠藤,進(jìn)行一個深刻的了解挟伙。從而達(dá)到我們想要的目的,我們會發(fā)現(xiàn)在此方法中存在一個關(guān)于非懶加載 Realize non-lazy classes (for +load methods and static instances)代碼如下

   for (EACH_HEADER) {
        classref_t const *classlist = 
            _getObjc2NonlazyClassList(hi, &count);
        for (i = 0; i < count; i++) {
            Class cls = remapClass(classlist[i]);
            const char *mangledName  = cls->mangledName();
   
            if (!cls) continue;

            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());
                }
    
            }  // alloc init - 類存在 完備 實例
            realizeClassWithoutSwift(cls, nil);
        }
    }

從此段代碼就知道只是針對非懶加載實現(xiàn)的類才走到這個方法中模孩,那么懶加載類又如何進(jìn)行加載的尖阔,那么就讓我們分別對這兩種情況進(jìn)行明確和詳細(xì)的學(xué)習(xí)和探索。

2.1榨咐、非懶加載類

在此方法中我們用斷點調(diào)試相關(guān)的程序介却,我們知道系統(tǒng)類的實現(xiàn)對我們來說是不可見的,所所以我們研究系統(tǒng)類的實現(xiàn)以及加載難度太大以及成本太高并且是得不償失块茁。所以我們研究自己定義的類最好不過齿坷,我們在自己實現(xiàn)的類中實現(xiàn)+load方法,

+ (void)load{
   NSLog(@"%s",__func__);
}

再次通過mangledName,斷點篩選出我們所定義的類的實現(xiàn)数焊,定位到當(dāng)前類

圖片.png

通過控制臺打印出相關(guān)結(jié)果:

(lldb) p/x LGPersonName
(const char *) $0 = 0x000000010032ad3e "LGPerson"

此時我們知道胃夏,此處的LGPersonName 還是一個帶地址的名字,并不是一個類昌跌,所以我們繼續(xù)探索究竟是什么時候我們所定義的類加載到內(nèi)存中仰禀,從而進(jìn)行相關(guān)的方法調(diào)用?

順著代碼查找到相關(guān)的realizeClassWithoutSwift定義下

static Class realizeClassWithoutSwift(Class cls, Class previously)

在中途通過mangledName篩選到我們當(dāng)前的類

if (!cls) return nil;
    if (cls->isRealized()) return cls;
    ASSERT(cls == remapClass(cls));
    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);
    }

以及

supercls = realizeClassWithoutSwift(remapClass(cls->superclass), nil);
   metacls = realizeClassWithoutSwift(remapClass(cls->ISA()), nil);

以上代碼主要的作用就是

  • 1 通過macho文件里的data返回一個類的class_ro_t 從而賦值到相應(yīng)的ro中 即clean-memory
  • 2 判斷是否是元類蚕愤,通過第一步的ro數(shù)據(jù)答恶,讀取到rw;
  • 3 如果非元類,寄進(jìn)行相應(yīng)的數(shù)據(jù)從rorw中萍诱,通過rw->set_ro(ro);操作完成
  • 4 完成相關(guān)類的繼承鏈悬嗓,從而為以后的方法查找以及后續(xù)的初始化埋下伏筆

雖然此時我們的類還只是帶地址的一個名字,并沒有實現(xiàn)裕坊,但是我們相關(guān)的rorw 是有值的包竹,驗證如下;

圖片.png

通過以上的方法我們知道實現(xiàn)了相關(guān)的類的信息籍凝,并且成功的將數(shù)據(jù)映射到內(nèi)存周瞎,包括rorw的賦值都在此時實現(xiàn),所以在此去實現(xiàn)相關(guān)的元類meta信息,從而在此進(jìn)入到最后的
methodizeClass(cls, previously); 中饵蒂;

圖片.png

跟著斷點調(diào)試進(jìn)入到attachToClass

void attachToClass(Class cls, Class previously, int flags)
    {
        runtimeLock.assertLocked();
        ASSERT((flags & ATTACH_CLASS) ||
               (flags & ATTACH_METACLASS) ||
               (flags & ATTACH_CLASS_AND_METACLASS));

        
        const char *mangledName  = cls->mangledName();
        const char *LGPersonName = "LGPerson";

        if (mcount > 0) {
        prepareMethodLists(cls, mlists + ATTACH_BUFSIZ - mcount, mcount, NO, fromBundle);
        rwe->methods.attachLists(mlists + ATTACH_BUFSIZ - mcount, mcount);
        if (flags & ATTACH_EXISTING) flushCaches(cls);
    }

從而進(jìn)行相關(guān)的類的方法声诸、協(xié)議、的實現(xiàn)退盯;

2.2彼乌、懶加載類

+load方法沒有實現(xiàn)的時候泻肯,我們會發(fā)現(xiàn)程序的執(zhí)行和之前的流程不太一樣,程序會先進(jìn)入到底層的objc_msgSent,因為我們在創(chuàng)建

LGPerson *person = [LGPerson alloc];

程序的底層會進(jìn)行一次消息轉(zhuǎn)發(fā)慰照,從而進(jìn)入到消息的查找從而進(jìn)入到lookUpImpOrForward

然后進(jìn)入程序?qū)崿F(xiàn)的判斷

  if (slowpath(!cls->isRealized())) {
        cls = realizeClassMaybeSwiftAndLeaveLocked(cls, runtimeLock);
        // runtimeLock may have been dropped but is now locked again
    }

進(jìn)入到realizeClassMaybeSwiftAndLeaveLocked 方法實現(xiàn)如下:

realizeClassMaybeSwiftMaybeRelock(Class cls, mutex_t& lock, bool leaveLocked)
{
    lock.assertLocked();
    
    const char *mangledName  = cls->mangledName();
    const char *LGPersonName = "LGPerson";

    if (strcmp(mangledName, LGPersonName) == 0) {
        auto kc_ro = (const class_ro_t *)cls->data();
        printf("%s: 這個是我要研究的 %s \n",__func__,LGPersonName);
    }

    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;
}

從而和非懶加載類一樣 執(zhí)行realizeClassWithoutSwiftmethodizeClass的流程灶挟,從而實現(xiàn)了該類的信息。

總結(jié):通過以上兩個類的對比和學(xué)習(xí)的過程毒租,對iOS開發(fā)過程中的類的懶加載非懶加載有了一個深刻的認(rèn)知稚铣,

三,分類的加載

3.1蝌衔、分類的概念和結(jié)構(gòu)

main.mm文件中我們通過手寫分類實現(xiàn)相關(guān)的定義

@interface LGPerson (LG)

@property (nonatomic, copy) NSString *cate_name;
@property (nonatomic, assign) int cate_age;

- (void)cate_instanceMethod1;
- (void)cate_instanceMethod3;
- (void)cate_instanceMethod2;
+ (void)cate_sayClassMethod;

@end

@implementation LGPerson (LG)

- (void)cate_instanceMethod1{
    NSLog(@"%s",__func__);
}

- (void)cate_instanceMethod3{
    NSLog(@"%s",__func__);
}

- (void)cate_instanceMethod2{
    NSLog(@"%s",__func__);
}

+ (void)cate_sayClassMethod{
    NSLog(@"%s",__func__);
}
@end

通過clang命令生成相關(guān)的cpp文件

進(jìn)入到這個文件中查詢到分類的結(jié)構(gòu)定義

// 分類 : 方法 - attachtoclass
struct _category_t {
    const char *name;
    struct _class_t *cls;
    const struct _method_list_t *instance_methods;
    const struct _method_list_t *class_methods;
    const struct _protocol_list_t *protocols;
    const struct _prop_list_t *properties;
};
  • 1 name代表分類的名字
  • 2 cls 代表分類的類
  • 3 instance_methods 實例方法榛泛,也即是對象方法
  • 4 class_methods 類方法
  • 5 protocols 協(xié)議列表
  • 6 properties 屬性列表

通過以上我們清楚的知道了一個分類的內(nèi)部結(jié)構(gòu)蝌蹂,那么分類是如何同類的加載載入到內(nèi)存的噩斟,接下來就進(jìn)入源碼分析流程;

3.2孤个、分類何時加載到應(yīng)用程序

隨即我們進(jìn)入到類的加載執(zhí)行的methodizeClass(cls, previously);
隨著代碼的注釋我們知道剃允,這就是程序加載分類的機制和時機

 // Attach categories - 分類 
    methodizeClass(cls, previously)

通過定位到我們的類然后進(jìn)行打印相關(guān)的列表


圖片.png

通過list 如果無序的時候我們可以進(jìn)行相關(guān)的排序操作

 method_list_t *list = ro->baseMethods();
    if (list) {
        prepareMethodLists(cls, &list, 1, YES, isBundleClass(cls));
        if (rwe) rwe->methods.attachLists(&list, 1);
    }

沒有進(jìn)行prepareMethodLists的結(jié)果是

(lldb) p list->get(0)
(method_t) $5 = {
name = "cate_instanceMethod1"
types = 0x0000000100001dfb "v16@0:8"
imp = 0x0000000100001a00 (KCObjc`-[LGPerson(LG) cate_instanceMethod1] at main.m:29)
}

p list->get(1)
(method_t) $6 = {
name = "cate_instanceMethod3"
types = 0x0000000100001dfb "v16@0:8"
imp = 0x0000000100001a30 (KCObjc`-[LGPerson(LG) cate_instanceMethod3] at main.m:33)
}

p list->get(2)
(method_t) $7 = {
name = "cate_instanceMethod2"
types = 0x0000000100001dfb "v16@0:8"
imp = 0x0000000100001a60 (KCObjc`-[LGPerson(LG) cate_instanceMethod2] at main.m:37)
}

而進(jìn)行prepareMethodLists的結(jié)果是

p list->get(0)
(method_t) $8 = {name = "cate_instanceMethod1"
types = 0x0000000100001dfb "v16@0:8"
imp = 0x0000000100001a00 (KCObjc`-[LGPerson(LG) cate_instanceMethod1] at main.m:29)
}

p list->get(1)
(method_t) $10 = {
name = "cate_instanceMethod2"
types = 0x0000000100001dfb "v16@0:8"
imp = 0x0000000100001a60 (KCObjc`-[LGPerson(LG) cate_instanceMethod2] at main.m:37)
}

p list->get(2)
(method_t) $9 = {
name = "cate_instanceMethod3"
types = 0x0000000100001dfb "v16@0:8"
imp = 0x0000000100001a30 (KCObjc`-[LGPerson(LG) cate_instanceMethod3] at main.m:33)
}

從而去加載相關(guān)的分類的屬性,方法齐鲤,協(xié)議等

 if (rwe && proplist) {
        rwe->properties.attachLists(&proplist, 1);
    }
 protocol_list_t *protolist = ro->baseProtocols;
    if (rwe && protolist) {
        rwe->protocols.attachLists(&protolist, 1);
    }
if (cls->isRootMetaclass()) {
        // root metaclass
        addMethod(cls, @selector(initialize), (IMP)&objc_noop_imp, "", NO);
    }

通過以上想分類的實現(xiàn)斥废,從而對內(nèi)存中的rwe進(jìn)行調(diào)用和開辟

 for (uint32_t i = 0; i < cats_count; i++) {
        auto& entry = cats_list[i];

        method_list_t *mlist = entry.cat->methodsForMeta(isMeta);
        if (mlist) {
            if (mcount == ATTACH_BUFSIZ) {
                prepareMethodLists(cls, mlists, mcount, NO, fromBundle);
                rwe->methods.attachLists(mlists, mcount);
                mcount = 0;
            }
            mlists[ATTACH_BUFSIZ - ++mcount] = mlist;
            fromBundle |= entry.hi->isBundle();
        }

        property_list_t *proplist =
            entry.cat->propertiesForMeta(isMeta, entry.hi);
        if (proplist) {
            if (propcount == ATTACH_BUFSIZ) {
                rwe->properties.attachLists(proplists, propcount);
                propcount = 0;
            }
            proplists[ATTACH_BUFSIZ - ++propcount] = proplist;
        }

        protocol_list_t *protolist = entry.cat->protocolsForMeta(isMeta);
        if (protolist) {
            if (protocount == ATTACH_BUFSIZ) {
                rwe->protocols.attachLists(protolists, protocount);
                protocount = 0;
            }
            protolists[ATTACH_BUFSIZ - ++protocount] = protolist;
        }
    }

通過以上的attachList方法,從而循環(huán)對分類的屬性给郊,協(xié)議,方法等操作具體實現(xiàn)如下

  • 1 判斷要添加的數(shù)量是否為0牡肉,如果是0 ,則直接返回
  • 2 然后對ATTACH_BUFSIZ - ++mcount 所有的方法進(jìn)行倒序插入
  • 3判斷 attachLists的list_array_tt 二維數(shù)組有多個一位數(shù)組淆九;

如果是统锤,說明多對多的關(guān)系
通過realloc對容器進(jìn)行重新分配,大小是原來的大小加上新增的大小
通過memmove把原來的數(shù)據(jù)移動到尾部
最后把新的數(shù)據(jù)拷貝到容器的起始位置炭庙,

  • 4 如果調(diào)用的attachLists的list_array_tt二維數(shù)組為空并且新增空間大小數(shù)目是1饲窿,則直接取attachList的第一個list
  • 5 如果當(dāng)前調(diào)用的attachLists的list_array_tt二維數(shù)組只有一個一位數(shù)組,

    如果是焕蹄,說明是一對多的關(guān)系
    通過realloc對容器進(jìn)行重新分配逾雄,大小是原來的大小加上新增的大小
    因為原來只有一個一位數(shù)組,所以直接賦值到新array的最后一個位置
    最后把新的數(shù)據(jù)拷貝到容器的起始位置

四腻脏,總結(jié)

這就是整個類鸦泳、分類的加載大致過程,雖然很多地方理解還不是很到位永品,但是我相信只要堅持下去辽故,總會有收獲的。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末腐碱,一起剝皮案震驚了整個濱河市誊垢,隨后出現(xiàn)的幾起案子掉弛,更是在濱河造成了極大的恐慌,老刑警劉巖喂走,帶你破解...
    沈念sama閱讀 206,378評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件殃饿,死亡現(xiàn)場離奇詭異,居然都是意外死亡芋肠,警方通過查閱死者的電腦和手機乎芳,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來帖池,“玉大人奈惑,你說我怎么就攤上這事∷冢” “怎么了肴甸?”我有些...
    開封第一講書人閱讀 152,702評論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長囚巴。 經(jīng)常有香客問我原在,道長,這世上最難降的妖魔是什么彤叉? 我笑而不...
    開封第一講書人閱讀 55,259評論 1 279
  • 正文 為了忘掉前任庶柿,我火速辦了婚禮,結(jié)果婚禮上秽浇,老公的妹妹穿的比我還像新娘浮庐。我一直安慰自己,他們只是感情好柬焕,可當(dāng)我...
    茶點故事閱讀 64,263評論 5 371
  • 文/花漫 我一把揭開白布审残。 她就那樣靜靜地躺著,像睡著了一般击喂。 火紅的嫁衣襯著肌膚如雪维苔。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,036評論 1 285
  • 那天懂昂,我揣著相機與錄音介时,去河邊找鬼。 笑死凌彬,一個胖子當(dāng)著我的面吹牛沸柔,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播铲敛,決...
    沈念sama閱讀 38,349評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼褐澎,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了伐蒋?” 一聲冷哼從身側(cè)響起工三,我...
    開封第一講書人閱讀 36,979評論 0 259
  • 序言:老撾萬榮一對情侶失蹤迁酸,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后俭正,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體奸鬓,經(jīng)...
    沈念sama閱讀 43,469評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,938評論 2 323
  • 正文 我和宋清朗相戀三年掸读,在試婚紗的時候發(fā)現(xiàn)自己被綠了串远。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,059評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡儿惫,死狀恐怖澡罚,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情肾请,我是刑警寧澤留搔,帶...
    沈念sama閱讀 33,703評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站筐喳,受9級特大地震影響催式,放射性物質(zhì)發(fā)生泄漏函喉。R本人自食惡果不足惜避归,卻給世界環(huán)境...
    茶點故事閱讀 39,257評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望管呵。 院中可真熱鬧梳毙,春花似錦、人聲如沸捐下。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,262評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽坷襟。三九已至奸柬,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間婴程,已是汗流浹背廓奕。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留档叔,地道東北人桌粉。 一個月前我還...
    沈念sama閱讀 45,501評論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像衙四,于是被迫代替她去往敵國和親铃肯。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,792評論 2 345