iOS Objective-C底層 part1:start

本文閱讀的objc源碼

打個符號斷點:_objc_init,可以看到以下調(diào)用棧

start.png

1. dyld 鏈接動態(tài)庫

app在被打開的時候所依賴的動態(tài)庫會被加載到程序中.
dyld(the dynamic link editor)
這樣一部手機(jī)所有app都共用系統(tǒng)的動態(tài)庫,這樣做大大的減小了可執(zhí)行文件(ipa)大小.
將模擬器路徑上的PGRuntimeTrain.app ShowInFinder,然后:

otool -L PGRuntimeTrain.app/PGRuntimeTrain
PGRuntimeTrain.app/PGRuntimeTrain:
    /System/Library/Frameworks/Foundation.framework/Foundation (compatibility version 300.0.0, current version 1349.54.0)
    /usr/lib/libobjc.A.dylib (compatibility version 1.0.0, current version 228.0.0)
    /usr/lib/libSystem.dylib (compatibility version 1.0.0, current version 1238.50.2)
    /System/Library/Frameworks/CoreFoundation.framework/CoreFoundation (compatibility version 150.0.0, current version 1349.55.0)
    /System/Library/Frameworks/UIKit.framework/UIKit (compatibility version 1.0.0, current version 3600.7.47)
Foundation.framework
CoreFoundation.framework
UIKit.framework
libobjc.A.dylib
├──objc
└──runtime
libSystem.dylib
├── libdispatch//GCD
├── libsystem_c//C語言庫
├── libsystem_blocks//Block
└── libcommonCrypto//加密庫剔猿,比如常用的 md5 函數(shù)

在做一些"逆"的工作時在這個環(huán)節(jié)可以加入自己寫的.dylib,功力不夠就不細(xì)說了

2. ImageLoader 加載鏡像文件

動態(tài)庫加載完成后就該加載我們自己的編寫的代碼編譯成的二進(jìn)制文件了,就是ImageLoaderXXXXXX系列方法.這些image內(nèi)就編譯著我們自己寫的符號、代碼等.

3. _objc_init runtime初始化

void _objc_init(void)
{
    static bool initialized = false;
    if (initialized) return;
    initialized = true;
    
   //讀取鏡像前,基本環(huán)境的初始化
    environ_init();
    tls_init();
    static_init();
    lock_init();
    exception_init();

    _dyld_objc_notify_register(&map_2_images, load_images, unmap_image);
}
3.1 map_2_images
map_2_images.png
map_2_images
└─map_images_nolock
  └─_read_images
  • map_images_nolock

map_images_nolock內(nèi)的已經(jīng)完成了:
header_info *hList[mhCount];==>類信息讀取到header_info的鏈表數(shù)組
preopt_init==>優(yōu)化共享緩存的初始化
sel_init==>初始化方法列表
arr_init==>初始化自動釋放池+散列表

  • _read_image

_read_image方法內(nèi)所有類的信息全放在header_info的鏈表數(shù)組之中,以該鏈表數(shù)組出發(fā):

void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int unoptimizedTotalClasses)
"IMAGE TIMES: first time tasks”—>support indexed_isa
"IMAGE TIMES: discover classes”—>_getObjc2ClassList
"IMAGE TIMES: remap classes”—>_getObjc2ClassRefs
"IMAGE TIMES: fix up selector references”—>_getObjc2SelectorRefs
"IMAGE TIMES: fix up objc_msgSend_fixup”—>_getObjc2MessageRefs
"IMAGE TIMES: discover protocols”—>_getObjc2ProtocolList
"IMAGE TIMES: fix up @protocol references”—>_getObjc2ProtocolRefs
"IMAGE TIMES: realize non-lazy classes”—>_getObjc2NonlazyClassList
"IMAGE TIMES: realize future classes”—>resolvedFutureClasses
"IMAGE TIMES: discover categories”—>_getObjc2CategoryList
  • 1.確認(rèn)是否支持indexed_isa

  • 2.讀取所有類信息

classref_t *classlist = _getObjc2ClassList(hi, &count);
for (i = 0; i < count; i++) {
    Class cls = (Class)classlist[i];
    Class newCls = readClass(cls, headerIsBundle, headerIsPreoptimized);
    
    if (newCls != cls  &&  newCls) {
        // Class was moved but not deleted. Currently this occurs
        // only when the new class resolved a future class.
        // Non-lazily realize the class below.
        resolvedFutureClasses = (Class *)
        realloc(resolvedFutureClasses,
                (resolvedFutureClassCount+1) * sizeof(Class));
        resolvedFutureClasses[resolvedFutureClassCount++] = newCls;
    }
}

當(dāng)調(diào)用readClass方法返回的Class newCls與傳入的cls不一樣時,則會將Class newCls加了數(shù)組Class *resolvedFutureClasses,方便后面對類做實現(xiàn)(第6步).

  • 3.讀取所有方法信息

  • 4.讀取所有協(xié)議信息

  • 5.實現(xiàn)所有的non-lazy classes

注意第2步:讀取所有類信息內(nèi)用的是:

classref_t *classlist = _getObjc2ClassList(hi, &count);

而現(xiàn)在用的是:

classref_t *classlist = _getObjc2NonlazyClassList(hi, &count);

實現(xiàn)了+load的類就是non-lazy classes,沒有實現(xiàn)+load的類就是lazy classes

realizeClass//cls->ro到cls->rw->ro
└─methodizeClass//向cls->rw內(nèi)填充方法列表,屬性列表,協(xié)議列表 
  └─attachCategories//讀取類對應(yīng)分類的內(nèi)容

5.1 realizeClass
realizeClass除了實現(xiàn)本類外,還實現(xiàn)本類的父類和元類.在此之前,cls->data()指向的是ro,在realizeClass內(nèi),完成了cls->rocls->rw->ro的轉(zhuǎn)換(字面也能看出來:ro->readonly,rw->readwrite,之后rw內(nèi)就可以寫入內(nèi)容了)

ro = (const class_ro_t *)cls->data();
if (ro->flags & RO_FUTURE) {
// This was a future class. rw data is already allocated.
      rw = cls->data();
      ro = cls->data()->ro;
      cls->changeInfo(RW_REALIZED|RW_REALIZING, RW_FUTURE);
} else {
// Normal class. Allocate writeable class data.
      rw = (class_rw_t *)calloc(sizeof(class_rw_t), 1);
      rw->ro = ro;
      rw->flags = RW_REALIZED|RW_REALIZING;
      cls->setData(rw);
}

5.2 methodizeClass
cls->rw內(nèi)填充方法列表,屬性列表,協(xié)議列表

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

property_list_t *proplist = ro->baseProperties;
if (proplist) {
    rw->properties.attachLists(&proplist, 1);
}

protocol_list_t *protolist = ro->baseProtocols;
if (protolist) {
    rw->protocols.attachLists(&protolist, 1);
}

5.3 attachCategories
讀取類對應(yīng)分類內(nèi)的方法列表,屬性列表,協(xié)議列表,填入cls->rw

static void
attachCategories(Class cls, category_list *cats, bool flush_caches)
{
    if (!cats) return;
    if (PrintReplacedMethods) printReplacements(cls, cats);

    bool isMeta = cls->isMetaClass();

    // fixme rearrange to remove these intermediate allocations
    method_list_t **mlists = (method_list_t **)
        malloc(cats->count * sizeof(*mlists));
    property_list_t **proplists = (property_list_t **)
        malloc(cats->count * sizeof(*proplists));
    protocol_list_t **protolists = (protocol_list_t **)
        malloc(cats->count * sizeof(*protolists));

    // Count backwards through cats to get newest categories first
    int mcount = 0;
    int propcount = 0;
    int protocount = 0;
    int i = cats->count;
    bool fromBundle = NO;
    while (i--) {
        auto& entry = cats->list[i];

        method_list_t *mlist = entry.cat->methodsForMeta(isMeta);
        if (mlist) {
            mlists[mcount++] = mlist;
            fromBundle |= entry.hi->isBundle();
        }

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

        protocol_list_t *protolist = entry.cat->protocols;
        if (protolist) {
            protolists[protocount++] = protolist;
        }
    }

    auto rw = cls->data();

    prepareMethodLists(cls, mlists, mcount, NO, fromBundle);
    rw->methods.attachLists(mlists, mcount);
    free(mlists);
    if (flush_caches  &&  mcount > 0) flushCaches(cls);

    rw->properties.attachLists(proplists, propcount);
    free(proplists);

    rw->protocols.attachLists(protolists, protocount);
    free(protolists);
}
  • 6.resolvedFutureClasses
if (resolvedFutureClasses) {
    for (i = 0; i < resolvedFutureClassCount; i++) {
        realizeClass(resolvedFutureClasses[i]);
        resolvedFutureClasses[i]->setInstancesRequireRawIsa(false/*inherited*/);
    }
    free(resolvedFutureClasses);
}

將第2步readClass中產(chǎn)生的Class *resolvedFutureClasses內(nèi)的類全部初始化.

  • 7.讀取所有分類信息

可能你會說不是在attachCategories方法內(nèi)讀取過類對應(yīng)分類的消息然后填入cls->rw嗎?
以上已經(jīng)說過了realizeClass初始化的是non-lazy class(實現(xiàn)了+load方法的類),所以還是有許多lazy class沒有初始化.

category_t **catlist = _getObjc2CategoryList(hi, &count);
Class cls = remapClass(cat->cls);

讀取分類列表,由單個分類(cat)反拿所屬類(cls),

bool classExists = NO;
if (cat->instanceMethods ||  cat->protocols
    ||  cat->instanceProperties)
{
    addUnattachedCategoryForClass(cat, cls, hi);
    if (cls->isRealized()) {
        remethodizeClass(cls);
        classExists = YES;
    }
    if (PrintConnecting) {
        _objc_inform("CLASS: found category -%s(%s) %s",
                     cls->nameForLogging(), cat->name,
                     classExists ? "on existing class" : "");
    }
}

if (cat->classMethods  ||  cat->protocols
    ||  (hasClassProperties && cat->_classProperties))
{
    addUnattachedCategoryForClass(cat, cls->ISA(), hi);
    if (cls->ISA()->isRealized()) {
        remethodizeClass(cls->ISA());
    }
    if (PrintConnecting) {
        _objc_inform("CLASS: found category +%s(%s)",
                     cls->nameForLogging(), cat->name);
    }
}

7.1 addUnattachedCategoryForClass綁定類與分類的關(guān)系.
7.2 如果滿足(cat->instanceMethods||cat->protocols||cat->instanceProperties),則看類有沒有被初始化cls->isRealized(),初始化了則調(diào)用remethodizeClass.
remethodizeClassmethodizeClass簡單了很多,因為調(diào)用remethodizeClass方法的情況下,類肯定是已經(jīng)被初始化了.當(dāng)然remethodizeClass最后也調(diào)用了attachCategories.

cat->instanceMethods
cat->protocols  
cat->instanceProperties

以上三項對應(yīng)類;

cat->classMethods
cat->protocols  
cat->_classProperties

以上三項則對應(yīng)類的元類;

當(dāng)然調(diào)用addUnattachedCategoryForClassremethodizeClass的邏輯,類與元類是一樣的.

存疑: realizeClass->methodizeClass->attachCategories
discover categories->remethodizeClass->attachCategories


two way

way1:初始化所有非懶加載類->填充3大列表->取出分類再填充3大列表
way2:拿出所有分類->反拿分類的類->類已經(jīng)初始化->取出分類再填充3大列表
好像做了重復(fù)的工作,存疑

3.2 load_images
load_images.png
void load_images(const char *path __unused, const struct mach_header *mh)
{
    if (!hasLoadMethods((const headerType *)mh)) return;

    recursive_mutex_locker_t lock(loadMethodLock);

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

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

load_images的功能會比map_2_images簡明很多,就是調(diào)用所有實現(xiàn)+load方法的類與分類.

    1. prepare_load_methods(生產(chǎn))
void prepare_load_methods(const headerType *mhdr)
{
    size_t count, i;

    runtimeLock.assertWriting();

    classref_t *classlist = 
        _getObjc2NonlazyClassList(mhdr, &count);
    for (i = 0; i < count; i++) {
        schedule_class_load(remapClass(classlist[i]));
    }

    category_t **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
        realizeClass(cls);
        assert(cls->ISA()->isRealized());
        add_category_to_loadable_list(cat);
    }
}
  • 生產(chǎn)類

注意這邊獲取non_lazy class列表的方法與先前講過獲取方法已經(jīng)不一樣了:

classref_t *classlist = _getObjc2NonlazyClassList(mhdr, &count);
extern classref_t *_getObjc2NonlazyClassList(const header_info *hi, size_t *count);
classref_t *classlist = _getObjc2NonlazyClassList(hi, &count);
extern classref_t *_getObjc2NonlazyClassList(const headerType *mhdr, size_t *count);

傳入的值由header_info變成了headerType.

在獲取所有non_lazy class后,調(diào)用schedule_class_load,將
non_lazy class加入靜態(tài)結(jié)構(gòu)體數(shù)組static struct loadable_class *loadable_classes;

struct loadable_class {
    Class cls;  // may be nil
    IMP method;
};
//cls->對應(yīng)類
//method->+load方法的地址

schedule_class_load內(nèi)有一個特殊處理:就是用類的父類遞歸調(diào)用schedule_class_load,這樣數(shù)組loadable_classes內(nèi)父類就排在前邊,父類的+load自然也就先調(diào)用.

static void schedule_class_load(Class cls)
{
    if (!cls) return;
    assert(cls->isRealized());  // _read_images should realize

    if (cls->data()->flags & RW_LOADED) return;

    // Ensure superclass-first ordering
    schedule_class_load(cls->superclass);

    add_class_to_loadable_list(cls);
    cls->setInfo(RW_LOADED); 
}
void add_class_to_loadable_list(Class cls)
{
    IMP method;

    loadMethodLock.assertLocked();

    method = cls->getLoadMethod();
    if (!method) return;  // Don't bother if cls has no +load method
    
    if (PrintLoading) {
        _objc_inform("LOAD: class '%s' scheduled for +load", 
                     cls->nameForLogging());
    }
    
    if (loadable_classes_used == loadable_classes_allocated) {
        loadable_classes_allocated = loadable_classes_allocated*2 + 16;
        loadable_classes = (struct loadable_class *)
            realloc(loadable_classes,
                              loadable_classes_allocated *
                              sizeof(struct loadable_class));
    }
    
    loadable_classes[loadable_classes_used].cls = cls;
    loadable_classes[loadable_classes_used].method = method;
    loadable_classes_used++;
}
  • 生產(chǎn)分類

處理好實現(xiàn)了+load的類后就是要處理實現(xiàn)了+load的分類了.

category_t **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
    realizeClass(cls);
    assert(cls->ISA()->isRealized());
    add_category_to_loadable_list(cat);
}
void add_category_to_loadable_list(Category cat)
{
    IMP method;

    loadMethodLock.assertLocked();

    method = _category_getLoadMethod(cat);

    // Don't bother if cat has no +load method
    if (!method) return;

    if (PrintLoading) {
        _objc_inform("LOAD: category '%s(%s)' scheduled for +load", 
                     _category_getClassName(cat), _category_getName(cat));
    }
    
    if (loadable_categories_used == loadable_categories_allocated) {
        loadable_categories_allocated = loadable_categories_allocated*2 + 16;
        loadable_categories = (struct loadable_category *)
            realloc(loadable_categories,
                              loadable_categories_allocated *
                              sizeof(struct loadable_category));
    }

    loadable_categories[loadable_categories_used].cat = cat;
    loadable_categories[loadable_categories_used].method = method;
    loadable_categories_used++;
}

獲取所有實現(xiàn)+load的分類:

category_t **categorylist = _getObjc2NonlazyCategoryList(mhdr, &count);

加入靜態(tài)結(jié)構(gòu)體數(shù)組static struct loadable_category *loadable_categories;

struct loadable_category {
    Category cat;  // may be nil
    IMP method;
};
//cat->對應(yīng)分類
//method->+load方法的地址
    1. call_load_methods(消費)

在做完前面的生產(chǎn)準(zhǔn)備工作之后,就是要消費準(zhǔn)備好的類和分類了.

void call_load_methods(void)
{
    static bool loading = NO;
    bool more_categories;

    loadMethodLock.assertLocked();

    // Re-entrant calls do nothing; the outermost call will finish the job.
    if (loading) return;
    loading = YES;

    void *pool = objc_autoreleasePoolPush();
    printf("call_load_methods up \n");
    do {
        printf("call_load_methods in \n");
        // 1. Repeatedly call class +loads until there aren't any more
        while (loadable_classes_used > 0) {
            call_class_loads();
        }

        // 2. Call category +loads ONCE
        more_categories = call_category_loads();
        
        // 3. Run more +loads if there are classes OR more untried categories
    } while (loadable_classes_used > 0  ||  more_categories);
    printf("call_load_methods down \n");
    objc_autoreleasePoolPop(pool);

    loading = NO;
}

順序是先類后分類,所以+load方法的調(diào)用順序可以總結(jié)為父類->類->分類.

  • 消費類

call_class_loads遍歷準(zhǔn)備好的loadable_classes內(nèi)的每一個struct loadable_class,調(diào)用(*load_method)(cls, SEL_load);

  • 消費分類

call_category_loads遍歷準(zhǔn)備好的loadable_categories內(nèi)的每一個struct loadable_category,調(diào)用(*load_method)(cls, SEL_load);

3.3 unmap_image

跑了源碼沒有調(diào)用過unmap_image,想來是針對一些讀取鏡像失敗的特殊情況的

在runtime初始化完成之際可以看到只有non_lazy class加載好了,其他的類還沒加載到內(nèi)存中,那這些類有事什么時候才加載那內(nèi)存中呢?這些類會在程序第一次用到這些類的時候才初始化到內(nèi)存中,類的+initialize方法也就是在此時被調(diào)用的.

所有流程可以合并以下流程圖:


_objc_init.png

文章參考:
objc源碼
我們的對象會經(jīng)歷什么

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末茫经,一起剝皮案震驚了整個濱河市打月,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖碎税,帶你破解...
    沈念sama閱讀 212,686評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異馏锡,居然都是意外死亡雷蹂,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,668評論 3 385
  • 文/潘曉璐 我一進(jìn)店門眷篇,熙熙樓的掌柜王于貴愁眉苦臉地迎上來萎河,“玉大人荔泳,你說我怎么就攤上這事蕉饼∨氨” “怎么了?”我有些...
    開封第一講書人閱讀 158,160評論 0 348
  • 文/不壞的土叔 我叫張陵昧港,是天一觀的道長擎椰。 經(jīng)常有香客問我,道長创肥,這世上最難降的妖魔是什么达舒? 我笑而不...
    開封第一講書人閱讀 56,736評論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮叹侄,結(jié)果婚禮上巩搏,老公的妹妹穿的比我還像新娘。我一直安慰自己趾代,他們只是感情好贯底,可當(dāng)我...
    茶點故事閱讀 65,847評論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著撒强,像睡著了一般禽捆。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上飘哨,一...
    開封第一講書人閱讀 50,043評論 1 291
  • 那天胚想,我揣著相機(jī)與錄音,去河邊找鬼芽隆。 笑死浊服,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的摆马。 我是一名探鬼主播臼闻,決...
    沈念sama閱讀 39,129評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼囤采!你這毒婦竟也來了述呐?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,872評論 0 268
  • 序言:老撾萬榮一對情侶失蹤蕉毯,失蹤者是張志新(化名)和其女友劉穎乓搬,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體代虾,經(jīng)...
    沈念sama閱讀 44,318評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡进肯,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,645評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了棉磨。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片江掩。...
    茶點故事閱讀 38,777評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出环形,到底是詐尸還是另有隱情策泣,我是刑警寧澤,帶...
    沈念sama閱讀 34,470評論 4 333
  • 正文 年R本政府宣布抬吟,位于F島的核電站萨咕,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏火本。R本人自食惡果不足惜危队,卻給世界環(huán)境...
    茶點故事閱讀 40,126評論 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望钙畔。 院中可真熱鬧茫陆,春花似錦、人聲如沸擎析。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,861評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽叔锐。三九已至挪鹏,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間愉烙,已是汗流浹背讨盒。 一陣腳步聲響...
    開封第一講書人閱讀 32,095評論 1 267
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留步责,地道東北人返顺。 一個月前我還...
    沈念sama閱讀 46,589評論 2 362
  • 正文 我出身青樓,卻偏偏與公主長得像蔓肯,于是被迫代替她去往敵國和親遂鹊。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,687評論 2 351

推薦閱讀更多精彩內(nèi)容