Objective-C runtime機(jī)制(9)——main函數(shù)前發(fā)生了什么

在我們的App代碼中,XCode會(huì)自動(dòng)創(chuàng)建一個(gè)main.m文件湾盒,其中定義了main函數(shù)

image

這里的main函數(shù)是我們整個(gè)App的入口湿右,它的調(diào)用時(shí)機(jī)甚至?xí)缬?code>AppDelegate 中的didFinishLaunching回調(diào)。

image

因此我們會(huì)說(shuō)罚勾,main函數(shù)是我們App程序的入口點(diǎn)函數(shù)毅人。

那么漾唉,我們App所運(yùn)行的第一個(gè)函數(shù),真的是main函數(shù)嗎堰塌?如果我們?cè)赬Code中設(shè)置符號(hào)斷點(diǎn)void _objc_init(void)赵刑,則會(huì)發(fā)現(xiàn),在進(jìn)入main函數(shù)之前场刑,其實(shí)系統(tǒng)還會(huì)調(diào)用void _objc_init(void) 方法:

image

這里的_objc_init方法般此,實(shí)際上是runtime的入口函數(shù)。

void _objc_init(void)

也就是說(shuō)牵现,在App的main函數(shù)之前铐懊,系統(tǒng)會(huì)首先對(duì)App的runtime運(yùn)行環(huán)境,做了一系列的初始化操作瞎疼。

而這個(gè)runtime入口函數(shù)科乎,又是被誰(shuí)調(diào)用起來(lái)的呢?答案是蘋(píng)果的動(dòng)態(tài)鏈接器dyld(the dynamic link editor)贼急。dyld是一個(gè)操作系統(tǒng)級(jí)的組件茅茂,它會(huì)負(fù)責(zé)iOS系統(tǒng)中每個(gè)App啟動(dòng)時(shí)的環(huán)境初始化以及動(dòng)態(tài)庫(kù)加載到內(nèi)存等一系列操作。
在系統(tǒng)內(nèi)核做好程序準(zhǔn)備工作之后太抓,交由dyld負(fù)責(zé)余下的工作空闲。

在這里再重申一遍,runtime的入口函數(shù)是_objc_init走敌,它是在main函數(shù)之前被dyld調(diào)用的碴倾。而+load()方法,則是在main函數(shù)前被_objc_init調(diào)用掉丽。今天跌榔,我們就來(lái)看一下,在main函數(shù)之前捶障,runtime究竟做了哪些初始化工作僧须。

Mach-O格式

在深入了解_objc_init的實(shí)現(xiàn)之前,我們需要先了解iOS系統(tǒng)中可執(zhí)行文件的文件格式:Mach-O格式残邀。關(guān)于Mach-O格式皆辽,我們?cè)?a href="http://www.reibang.com/p/59256f511fa6" target="_blank">Objective-C runtime機(jī)制(前傳)——Mach-O格式柑蛇,Objective-C runtime機(jī)制(前傳2)——Mach-O格式和runtime中以及介紹過(guò)芥挣。

我們可以在XCode 工程 product文件夾下找到RuntimeEnter.app工程,用finder打開(kāi)所在目錄耻台,其實(shí)RuntimeEnter.app是一個(gè)壓縮包空免,用鼠標(biāo)右鍵選擇show Package Contents ,可以看到下面有這些文件盆耽,其中和我們工程同名的可運(yùn)行程序就是Mach-O格式的可運(yùn)行文件:


image

_objc_init

我們?cè)趇OS App中設(shè)置符號(hào)斷點(diǎn)_objc_init蹋砚,則在App啟動(dòng)時(shí)(進(jìn)入main函數(shù)之前)扼菠,會(huì)進(jìn)入如下調(diào)用堆棧:

image

可以看到,其底層是由dyld調(diào)用起來(lái)的坝咐。關(guān)于dyld我們不去多說(shuō)循榆,讓我們看一下runtime中_objc_init的定義:

/***********************************************************************
* _objc_init
* Bootstrap initialization. Registers our image notifier with dyld.
* Called by libSystem BEFORE library initialization time
**********************************************************************/

void _objc_init(void)
{
    static bool initialized = false;
    if (initialized) return;
    initialized = true;
    
    // fixme defer initialization until an objc-using image is found?
    environ_init();
    tls_init();
    static_init();
    lock_init();
    exception_init();

    _dyld_objc_notify_register(&map_images, load_images, unmap_image);
}

除去上面一堆init方法,我們重點(diǎn)關(guān)注

 _dyld_objc_notify_register(&map_images, load_images, unmap_image);

_dyld_objc_notify_register方法注冊(cè)了對(duì)dyld中關(guān)于加載images的事件回調(diào):

//
// Note: only for use by objc runtime
// Register handlers to be called when objc images are mapped, unmapped, and initialized.
// Dyld will call back the "mapped" function with an array of images that contain an objc-image-info section.
// Those images that are dylibs will have the ref-counts automatically bumped, so objc will no longer need to
// call dlopen() on them to keep them from being unloaded.  During the call to _dyld_objc_notify_register(),
// dyld will call the "mapped" function with already loaded objc images.  During any later dlopen() call,
// dyld will also call the "mapped" function.  Dyld will call the "init" function when dyld would be called
// initializers in that image.  This is when objc calls any +load methods in that image.
//
void _dyld_objc_notify_register(_dyld_objc_notify_mapped    mapped,
                                _dyld_objc_notify_init      init,
                                _dyld_objc_notify_unmapped  unmapped);

分別注冊(cè)了那些事件呢墨坚?根據(jù)注釋秧饮,我們可以知道,共注冊(cè)了三個(gè)事件的回調(diào):

  • _dyld_objc_notify_mapped : OC image被加載映射到內(nèi)存(+load()方法在此時(shí)被調(diào)用)
  • _dyld_objc_notify_init : OC image被init時(shí)
  • _dyld_objc_notify_unmapped : OC image被移除內(nèi)存時(shí)

以上三個(gè)回調(diào)類型是用的函數(shù)指針泽篮,定義為

typedef void (*_dyld_objc_notify_mapped)(unsigned count, const char* const paths[], const struct mach_header* const mh[]);
typedef void (*_dyld_objc_notify_init)(const char* path, const struct mach_header* mh);
typedef void (*_dyld_objc_notify_unmapped)(const char* path, const struct mach_header* mh);

_dyld_objc_notify_mapped

當(dāng)image被dyld加載到內(nèi)存后盗尸,會(huì)調(diào)用回調(diào)_dyld_objc_notify_mapped 。在runtime中帽撑,對(duì)應(yīng)的函數(shù)是:

void
map_images(unsigned count, const char * const paths[],
           const struct mach_header * const mhdrs[])
{
    rwlock_writer_t lock(runtimeLock);
    return map_images_nolock(count, paths, mhdrs);
}

void 
map_images_nolock(unsigned mhCount, const char * const mhPaths[],
                  const struct mach_header * const mhdrs[])
{
    static bool firstTime = YES;
    header_info *hList[mhCount];
    uint32_t hCount;
    size_t selrefCount = 0;

    // Perform first-time initialization if necessary.
    // This function is called before ordinary library initializers. 
    // fixme defer initialization until an objc-using image is found?
    if (firstTime) {
        preopt_init();
    }

    if (PrintImages) {
        _objc_inform("IMAGES: processing %u newly-mapped images...\n", mhCount);
    }


    // Find all images with Objective-C metadata.
    hCount = 0;

    // Count classes. Size various table based on the total.
    int totalClasses = 0;
    int unoptimizedTotalClasses = 0;
    {
        uint32_t i = mhCount;
        while (i--) {
            const headerType *mhdr = (const headerType *)mhdrs[I];

            auto hi = addHeader(mhdr, mhPaths[i], totalClasses, unoptimizedTotalClasses);
            if (!hi) {
                // no objc data in this entry
                continue;
            }
            
            if (mhdr->filetype == MH_EXECUTE) {
                // Size some data structures based on main executable's size
#if __OBJC2__
                size_t count;
                _getObjc2SelectorRefs(hi, &count);
                selrefCount += count;
                _getObjc2MessageRefs(hi, &count);
                selrefCount += count;
#else
                _getObjcSelectorRefs(hi, &selrefCount);
#endif
                
#if SUPPORT_GC_COMPAT
                // Halt if this is a GC app.
                if (shouldRejectGCApp(hi)) {
                    _objc_fatal_with_reason
                        (OBJC_EXIT_REASON_GC_NOT_SUPPORTED, 
                         OS_REASON_FLAG_CONSISTENT_FAILURE, 
                         "Objective-C garbage collection " 
                         "is no longer supported.");
                }
#endif
            }
            
            hList[hCount++] = hi;
            
            if (PrintImages) {
                _objc_inform("IMAGES: loading image for %s%s%s%s%s\n", 
                             hi->fname(),
                             mhdr->filetype == MH_BUNDLE ? " (bundle)" : "",
                             hi->info()->isReplacement() ? " (replacement)" : "",
                             hi->info()->hasCategoryClassProperties() ? " (has class properties)" : "",
                             hi->info()->optimizedByDyld()?" (preoptimized)":"");
            }
        }
    }

    // Perform one-time runtime initialization that must be deferred until 
    // the executable itself is found. This needs to be done before 
    // further initialization.
    // (The executable may not be present in this infoList if the 
    // executable does not contain Objective-C code but Objective-C 
    // is dynamically loaded later.
    if (firstTime) {
        sel_init(selrefCount);
        arr_init();

#if SUPPORT_GC_COMPAT
        // Reject any GC images linked to the main executable.
        // We already rejected the app itself above.
        // Images loaded after launch will be rejected by dyld.

        for (uint32_t i = 0; i < hCount; i++) {
            auto hi = hList[I];
            auto mh = hi->mhdr();
            if (mh->filetype != MH_EXECUTE  &&  shouldRejectGCImage(mh)) {
                _objc_fatal_with_reason
                    (OBJC_EXIT_REASON_GC_NOT_SUPPORTED, 
                     OS_REASON_FLAG_CONSISTENT_FAILURE, 
                     "%s requires Objective-C garbage collection "
                     "which is no longer supported.", hi->fname());
            }
        }
#endif

#if TARGET_OS_OSX
        // Disable +initialize fork safety if the app is too old (< 10.13).
        // Disable +initialize fork safety if the app has a
        //   __DATA,__objc_fork_ok section.

        if (dyld_get_program_sdk_version() < DYLD_MACOSX_VERSION_10_13) {
            DisableInitializeForkSafety = true;
            if (PrintInitializing) {
                _objc_inform("INITIALIZE: disabling +initialize fork "
                             "safety enforcement because the app is "
                             "too old (SDK version " SDK_FORMAT ")",
                             FORMAT_SDK(dyld_get_program_sdk_version()));
            }
        }

        for (uint32_t i = 0; i < hCount; i++) {
            auto hi = hList[I];
            auto mh = hi->mhdr();
            if (mh->filetype != MH_EXECUTE) continue;
            unsigned long size;
            if (getsectiondata(hi->mhdr(), "__DATA", "__objc_fork_ok", &size)) {
                DisableInitializeForkSafety = true;
                if (PrintInitializing) {
                    _objc_inform("INITIALIZE: disabling +initialize fork "
                                 "safety enforcement because the app has "
                                 "a __DATA,__objc_fork_ok section");
                }
            }
            break;  // assume only one MH_EXECUTE image
        }
#endif

    }

    if (hCount > 0) {
        _read_images(hList, hCount, totalClasses, unoptimizedTotalClasses);
    }

    firstTime = NO;
}

map_images方法實(shí)質(zhì)上會(huì)調(diào)用map_images_nolock方法泼各。
而在map_images_nolock內(nèi)部,又調(diào)用了_read_images方法亏拉,其核心是用來(lái)讀取Mach-O格式文件的runtime相關(guān)的section信息扣蜻,并轉(zhuǎn)化為runtime內(nèi)部的數(shù)據(jù)結(jié)構(gòu)

我們來(lái)看一下_read_images 方法的實(shí)現(xià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;
    TimeLogger ts(PrintImageTimes);

    runtimeLock.assertWriting();

#define EACH_HEADER \
    hIndex = 0;         \
    hIndex < hCount && (hi = hList[hIndex]); \
    hIndex++

    if (!doneOnce) {
        doneOnce = YES;

#if SUPPORT_NONPOINTER_ISA
        // Disable non-pointer isa under some conditions.

# if SUPPORT_INDEXED_ISA
        // Disable nonpointer isa if any image contains old Swift code
        for (EACH_HEADER) {
            if (hi->info()->containsSwift()  &&
                hi->info()->swiftVersion() < objc_image_info::SwiftVersion3)
            {
                DisableNonpointerIsa = true;
                if (PrintRawIsa) {
                    _objc_inform("RAW ISA: disabling non-pointer isa because "
                                 "the app or a framework contains Swift code "
                                 "older than Swift 3.0");
                }
                break;
            }
        }
# endif

# if TARGET_OS_OSX
        // Disable non-pointer isa if the app is too old
        // (linked before OS X 10.11)
        if (dyld_get_program_sdk_version() < DYLD_MACOSX_VERSION_10_11) {
            DisableNonpointerIsa = true;
            if (PrintRawIsa) {
                _objc_inform("RAW ISA: disabling non-pointer isa because "
                             "the app is too old (SDK version " SDK_FORMAT ")",
                             FORMAT_SDK(dyld_get_program_sdk_version()));
            }
        }

        // Disable non-pointer isa if the app has a __DATA,__objc_rawisa section
        // New apps that load old extensions may need this.
        for (EACH_HEADER) {
            if (hi->mhdr()->filetype != MH_EXECUTE) continue;
            unsigned long size;
            if (getsectiondata(hi->mhdr(), "__DATA", "__objc_rawisa", &size)) {
                DisableNonpointerIsa = true;
                if (PrintRawIsa) {
                    _objc_inform("RAW ISA: disabling non-pointer isa because "
                                 "the app has a __DATA,__objc_rawisa section");
                }
            }
            break;  // assume only one MH_EXECUTE image
        }
# endif

#endif

        if (DisableTaggedPointers) {
            disableTaggedPointers();
        }
        
        if (PrintConnecting) {
            _objc_inform("CLASS: found %d classes during launch", totalClasses);
        }

        // namedClasses
        // Preoptimized classes don't go in this table.
        // 4/3 is NXMapTable's load factor
        int namedClassesSize = 
            (isPreoptimized() ? unoptimizedTotalClasses : totalClasses) * 4 / 3;
        gdb_objc_realized_classes =
            NXCreateMapTable(NXStrValueMapPrototype, namedClassesSize);

        ts.log("IMAGE TIMES: first time tasks");
    }


    // Discover classes. Fix up unresolved future classes. Mark bundle classes.

    for (EACH_HEADER) {
        if (! mustReadClasses(hi)) {
            // Image is sufficiently optimized that we need not call readClass()
            continue;
        }

        bool headerIsBundle = hi->isBundle();
        bool headerIsPreoptimized = hi->isPreoptimized();

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

    ts.log("IMAGE TIMES: discover classes");

    // Fix up remapped classes
    // Class list and nonlazy class list remain unremapped.
    // Class refs and super refs are remapped for message dispatching.
    
    if (!noClassesRemapped()) {
        for (EACH_HEADER) {
            Class *classrefs = _getObjc2ClassRefs(hi, &count);
            for (i = 0; i < count; i++) {
                remapClassRef(&classrefs[I]);
            }
            // fixme why doesn't test future1 catch the absence of this?
            classrefs = _getObjc2SuperRefs(hi, &count);
            for (i = 0; i < count; i++) {
                remapClassRef(&classrefs[I]);
            }
        }
    }

    ts.log("IMAGE TIMES: remap classes");

    // Fix up @selector references
    static size_t UnfixedSelectors;
    sel_lock();
    for (EACH_HEADER) {
        if (hi->isPreoptimized()) continue;

        bool isBundle = hi->isBundle();
        SEL *sels = _getObjc2SelectorRefs(hi, &count);
        UnfixedSelectors += count;
        for (i = 0; i < count; i++) {
            const char *name = sel_cname(sels[i]);
            sels[i] = sel_registerNameNoLock(name, isBundle);
        }
    }
    sel_unlock();

    ts.log("IMAGE TIMES: fix up selector references");

#if SUPPORT_FIXUP
    // Fix up old objc_msgSend_fixup call sites
    for (EACH_HEADER) {
        message_ref_t *refs = _getObjc2MessageRefs(hi, &count);
        if (count == 0) continue;

        if (PrintVtables) {
            _objc_inform("VTABLES: repairing %zu unsupported vtable dispatch "
                         "call sites in %s", count, hi->fname());
        }
        for (i = 0; i < count; i++) {
            fixupMessageRef(refs+i);
        }
    }

    ts.log("IMAGE TIMES: fix up objc_msgSend_fixup");
#endif

    // Discover protocols. Fix up protocol refs.
    for (EACH_HEADER) {
        extern objc_class OBJC_CLASS_$_Protocol;
        Class cls = (Class)&OBJC_CLASS_$_Protocol;
        assert(cls);
        NXMapTable *protocol_map = protocols();
        bool isPreoptimized = hi->isPreoptimized();
        bool isBundle = hi->isBundle();

        protocol_t **protolist = _getObjc2ProtocolList(hi, &count);
        for (i = 0; i < count; i++) {
            readProtocol(protolist[i], cls, protocol_map, 
                         isPreoptimized, isBundle);
        }
    }

    ts.log("IMAGE TIMES: discover protocols");

    // Fix up @protocol references
    // Preoptimized images may have the right 
    // answer already but we don't know for sure.
    for (EACH_HEADER) {
        protocol_t **protolist = _getObjc2ProtocolRefs(hi, &count);
        for (i = 0; i < count; i++) {
            remapProtocolRef(&protolist[I]);
        }
    }

    ts.log("IMAGE TIMES: fix up @protocol references");

    // Realize non-lazy classes (for +load methods and static instances)
    for (EACH_HEADER) {
        classref_t *classlist = 
            _getObjc2NonlazyClassList(hi, &count);
        for (i = 0; i < count; i++) {
            Class cls = remapClass(classlist[i]);
            if (!cls) continue;

            // hack for class __ARCLite__, which didn't get this above
#if TARGET_OS_SIMULATOR
            if (cls->cache._buckets == (void*)&_objc_empty_cache  &&  
                (cls->cache._mask  ||  cls->cache._occupied)) 
            {
                cls->cache._mask = 0;
                cls->cache._occupied = 0;
            }
            if (cls->ISA()->cache._buckets == (void*)&_objc_empty_cache  &&  
                (cls->ISA()->cache._mask  ||  cls->ISA()->cache._occupied)) 
            {
                cls->ISA()->cache._mask = 0;
                cls->ISA()->cache._occupied = 0;
            }
#endif

            realizeClass(cls);
        }
    }

    ts.log("IMAGE TIMES: realize non-lazy classes");

    // Realize newly-resolved future classes, in case CF manipulates them
    if (resolvedFutureClasses) {
        for (i = 0; i < resolvedFutureClassCount; i++) {
            realizeClass(resolvedFutureClasses[I]);
            resolvedFutureClasses[i]->setInstancesRequireRawIsa(false/*inherited*/);
        }
        free(resolvedFutureClasses);
    }    

    ts.log("IMAGE TIMES: realize future classes");

    // Discover categories. 
    for (EACH_HEADER) {
        category_t **catlist = 
            _getObjc2CategoryList(hi, &count);
        bool hasClassProperties = hi->info()->hasCategoryClassProperties();

        for (i = 0; i < count; i++) {
            category_t *cat = catlist[I];
            Class cls = remapClass(cat->cls);

            if (!cls) {
                // Category's target class is missing (probably weak-linked).
                // Disavow any knowledge of this category.
                catlist[i] = nil;
                if (PrintConnecting) {
                    _objc_inform("CLASS: IGNORING category \?\?\?(%s) %p with "
                                 "missing weak-linked target class", 
                                 cat->name, cat);
                }
                continue;
            }

            // Process this category. 
            // First, register the category with its target class. 
            // Then, rebuild the class's method lists (etc) if 
            // the class is realized. 
            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);
                }
            }
        }
    }

    ts.log("IMAGE TIMES: discover categories");

    // Category discovery MUST BE LAST to avoid potential races 
    // when other threads call the new category code before 
    // this thread finishes its fixups.

    // +load handled by prepare_load_methods()

    if (DebugNonFragileIvars) {
        realizeAllClasses();
    }


    // Print preoptimization statistics
    if (PrintPreopt) {
        static unsigned int PreoptTotalMethodLists;
        static unsigned int PreoptOptimizedMethodLists;
        static unsigned int PreoptTotalClasses;
        static unsigned int PreoptOptimizedClasses;

        for (EACH_HEADER) {
            if (hi->isPreoptimized()) {
                _objc_inform("PREOPTIMIZATION: honoring preoptimized selectors "
                             "in %s", hi->fname());
            }
            else if (hi->info()->optimizedByDyld()) {
                _objc_inform("PREOPTIMIZATION: IGNORING preoptimized selectors "
                             "in %s", hi->fname());
            }

            classref_t *classlist = _getObjc2ClassList(hi, &count);
            for (i = 0; i < count; i++) {
                Class cls = remapClass(classlist[i]);
                if (!cls) continue;

                PreoptTotalClasses++;
                if (hi->isPreoptimized()) {
                    PreoptOptimizedClasses++;
                }
                
                const method_list_t *mlist;
                if ((mlist = ((class_ro_t *)cls->data())->baseMethods())) {
                    PreoptTotalMethodLists++;
                    if (mlist->isFixedUp()) {
                        PreoptOptimizedMethodLists++;
                    }
                }
                if ((mlist=((class_ro_t *)cls->ISA()->data())->baseMethods())) {
                    PreoptTotalMethodLists++;
                    if (mlist->isFixedUp()) {
                        PreoptOptimizedMethodLists++;
                    }
                }
            }
        }

        _objc_inform("PREOPTIMIZATION: %zu selector references not "
                     "pre-optimized", UnfixedSelectors);
        _objc_inform("PREOPTIMIZATION: %u/%u (%.3g%%) method lists pre-sorted",
                     PreoptOptimizedMethodLists, PreoptTotalMethodLists, 
                     PreoptTotalMethodLists
                     ? 100.0*PreoptOptimizedMethodLists/PreoptTotalMethodLists 
                     : 0.0);
        _objc_inform("PREOPTIMIZATION: %u/%u (%.3g%%) classes pre-registered",
                     PreoptOptimizedClasses, PreoptTotalClasses, 
                     PreoptTotalClasses 
                     ? 100.0*PreoptOptimizedClasses/PreoptTotalClasses
                     : 0.0);
        _objc_inform("PREOPTIMIZATION: %zu protocol references not "
                     "pre-optimized", UnfixedProtocolReferences);
    }

#undef EACH_HEADER
}

_read_images 方法寫(xiě)了很長(zhǎng)及塘,其實(shí)就是做了一件事弱贼,將Mach-O文件的section依次讀取,并根據(jù)內(nèi)容初始化runtime的內(nèi)存結(jié)構(gòu)磷蛹。

根據(jù)注釋吮旅,_read_images 方法主要做了下面這些事情:

  1. 是否需要禁用isa優(yōu)化。這里有三種情況:使用了swift 3.0前的swift代碼味咳。OSX版本早于10.11庇勃。在OSX系統(tǒng)下,Mach-O的DATA段明確指明了__objc_rawisa(不使用優(yōu)化的isa)槽驶。
  2. __objc_classlist section中讀取class list
  3. __objc_classrefs section中讀取class 引用的信息责嚷,并調(diào)用remapClassRef方法來(lái)處理。
  4. __objc_selrefs section中讀取selector的引用信息掂铐,并調(diào)用sel_registerNameNoLock方法處理罕拂。
  5. __objc_protolist section中讀取cls的Protocol信息,并調(diào)用readProtocol方法來(lái)讀取Protocol信息全陨。
  6. __objc_protorefs section中讀取protocol的ref信息爆班,并調(diào)用remapProtocolRef方法來(lái)處理。
  7. __objc_nlclslist section中讀取non-lazy class信息辱姨,并調(diào)用static Class realizeClass(Class cls)方法來(lái)實(shí)現(xiàn)這些class柿菩。realizeClass方法核心是初始化objc_class數(shù)據(jù)結(jié)構(gòu),賦予初始值雨涛。
  8. __objc_catlist section中讀取category信息枢舶,并調(diào)用addUnattachedCategoryForClass方法來(lái)為類或元類添加對(duì)應(yīng)的方法懦胞,屬性和協(xié)議。關(guān)于Category凉泄,我們?cè)谇懊娴奈恼轮絮镂荆呀?jīng)有過(guò)相關(guān)的討論。

OK后众,以上就是在dyld將image map到內(nèi)存后醇份,runtime所做的事情:**根據(jù)Mach-O相關(guān)section中的信息,來(lái)初始化runtim的內(nèi)存結(jié)構(gòu)吼具。**

_dyld_objc_notify_init

當(dāng)dyld要init image的時(shí)候僚纷,會(huì)產(chǎn)生_dyld_objc_notify_init通知。在runime中, 是通過(guò)load_images方法做回調(diào)響應(yīng)的拗盒。

void
load_images(const char *path __unused, const struct mach_header *mh)
{
    // 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
    {
        rwlock_writer_t lock2(runtimeLock);
        prepare_load_methods((const headerType *)mh);
    }

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

在load_images方法其實(shí)就是干了一件事怖竭,調(diào)用Class的+load()方法。

runtime調(diào)用+load()方法分為兩步走:

  1. Discover load methods
  2. Call +load methods

Discover load methods

在Discover load methods中陡蝇,會(huì)調(diào)用prepare_load_methods 來(lái)處理+load 方法:

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

    runtimeLock.assertWriting();

    // 添加 class 到 call +load 隊(duì)列(super class 會(huì)在 subclass之前)
    classref_t *classlist = 
        _getObjc2NonlazyClassList(mhdr, &count);
    for (i = 0; i < count; i++) {
        schedule_class_load(remapClass(classlist[i]));
    }

    // 添加 category到call +load 隊(duì)列
    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);
    }
}

prepare_load_methods 邏輯分為兩個(gè)部分:

  1. 調(diào)用schedule_class_load 來(lái)組織class的+load 方法
  2. 調(diào)用add_category_to_loadable_list來(lái)組織category的+load方法

schedule_class_load 方法實(shí)現(xiàn)如下:

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

這是一個(gè)遞歸調(diào)用痊臭,會(huì)先把superclass用add_class_to_loadable_list方法到loadable class list中:

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

static struct loadable_class *loadable_classes = nil;

從上面的方法可以看出,每一個(gè)定義了+load的類登夫,都會(huì)被放到loadable_classes中广匙。

因此,+load方法并不存在子類重寫(xiě)父類之說(shuō)恼策。而且父類的+load方法會(huì)先于子類調(diào)用鸦致。

在來(lái)看一下add_category_to_loadable_list方法:

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

static struct loadable_category *loadable_categories = nil;

add_category_to_loadable_list 方法中,會(huì)將所有定義了+load方法的category都放到loadable_categories隊(duì)列中涣楷。

Call +load methods

將定義了+load方法的class和category分別放到loadable_classesloadable_categories 隊(duì)列后分唾,runtime就會(huì)依次讀取隊(duì)列中的class和category,并為之調(diào)用+load方法:

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();

    do {
        // 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);

    objc_autoreleasePoolPop(pool);

    loading = NO;
}

從上面的call_load_methods方法可以看出:

  1. super class的+load方法調(diào)用時(shí)機(jī)是先于sub class的
  2. class的+load方法調(diào)用時(shí)機(jī)是先于category的

_dyld_objc_notify_unmapped

當(dāng)dyld要將image移除內(nèi)存時(shí)狮斗,會(huì)發(fā)送_dyld_objc_notify_unmapped通知绽乔。在runtime中,是用unmap_image方法來(lái)響應(yīng)的碳褒。

void 
unmap_image(const char *path __unused, const struct mach_header *mh)
{
    recursive_mutex_locker_t lock(loadMethodLock);
    rwlock_writer_t lock2(runtimeLock);
    unmap_image_nolock(mh);
}

void 
unmap_image_nolock(const struct mach_header *mh)
{
    if (PrintImages) {
        _objc_inform("IMAGES: processing 1 newly-unmapped image...\n");
    }

    header_info *hi;
    
    // Find the runtime's header_info struct for the image
    for (hi = FirstHeader; hi != NULL; hi = hi->getNext()) {
        if (hi->mhdr() == (const headerType *)mh) {
            break;
        }
    }

    if (!hi) return;

    if (PrintImages) {
        _objc_inform("IMAGES: unloading image for %s%s%s\n", 
                     hi->fname(),
                     hi->mhdr()->filetype == MH_BUNDLE ? " (bundle)" : "",
                     hi->info()->isReplacement() ? " (replacement)" : "");
    }

    _unload_image(hi);

    // Remove header_info from header list
    removeHeader(hi);
    free(hi);
}

主要是做了header信息的移除折砸。

總結(jié)

在本篇文章中,我們知道了dyld在main()函數(shù)之前沙峻,會(huì)調(diào)用runtime的_objc_init 方法睦授。_objc_init是runtime的入口函數(shù),它會(huì)根據(jù)Mach-O文件中相關(guān)的section信息來(lái)初始化runtime內(nèi)存空間专酗。比如睹逃,加載class,protocol祷肯,以及附加category到class沉填,調(diào)用+load方法等。

當(dāng)然佑笋,在main()函數(shù)前翼闹,dyld除了調(diào)用_objc_init 外,還會(huì)做許多其他的操作蒋纬。如將動(dòng)態(tài)鏈接庫(kù)加載入內(nèi)存猎荠。但這就不屬于runtime的范疇了,我們不去深究蜀备。

當(dāng)dyld將我們App的運(yùn)行環(huán)境都準(zhǔn)備好后关摇,dyld 會(huì)清理現(xiàn)場(chǎng),將調(diào)用椖敫螅回歸输虱,調(diào)用main()函數(shù),這時(shí)候脂凶,我們的App就算啟動(dòng)了:


image
int main(int argc, char * argv[]) {
    @autoreleasepool {
        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
    }
}

在main()函數(shù)被調(diào)用前宪睹,系統(tǒng)其實(shí)已經(jīng)為我們做了很多的準(zhǔn)備工作。就像sunnyxx在其博客中說(shuō)的:

孤獨(dú)的 main 函數(shù)蚕钦,看上去是程序的開(kāi)始亭病,卻是一段精彩的終結(jié)

參考資料

dyld詳解
iOS 程序 main 函數(shù)之前發(fā)生了什么

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市嘶居,隨后出現(xiàn)的幾起案子罪帖,更是在濱河造成了極大的恐慌,老刑警劉巖邮屁,帶你破解...
    沈念sama閱讀 217,185評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件胸蛛,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡樱报,警方通過(guò)查閱死者的電腦和手機(jī)葬项,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,652評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)迹蛤,“玉大人民珍,你說(shuō)我怎么就攤上這事〉领” “怎么了嚷量?”我有些...
    開(kāi)封第一講書(shū)人閱讀 163,524評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)逆趣。 經(jīng)常有香客問(wèn)我蝶溶,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,339評(píng)論 1 293
  • 正文 為了忘掉前任抖所,我火速辦了婚禮梨州,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘田轧。我一直安慰自己暴匠,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,387評(píng)論 6 391
  • 文/花漫 我一把揭開(kāi)白布傻粘。 她就那樣靜靜地躺著每窖,像睡著了一般。 火紅的嫁衣襯著肌膚如雪弦悉。 梳的紋絲不亂的頭發(fā)上窒典,一...
    開(kāi)封第一講書(shū)人閱讀 51,287評(píng)論 1 301
  • 那天,我揣著相機(jī)與錄音稽莉,去河邊找鬼瀑志。 笑死,一個(gè)胖子當(dāng)著我的面吹牛肩祥,可吹牛的內(nèi)容都是我干的后室。 我是一名探鬼主播,決...
    沈念sama閱讀 40,130評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼混狠,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼岸霹!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起将饺,我...
    開(kāi)封第一講書(shū)人閱讀 38,985評(píng)論 0 275
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤贡避,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后予弧,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體刮吧,經(jīng)...
    沈念sama閱讀 45,420評(píng)論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,617評(píng)論 3 334
  • 正文 我和宋清朗相戀三年掖蛤,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了杀捻。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,779評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡蚓庭,死狀恐怖致讥,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情器赞,我是刑警寧澤垢袱,帶...
    沈念sama閱讀 35,477評(píng)論 5 345
  • 正文 年R本政府宣布,位于F島的核電站港柜,受9級(jí)特大地震影響请契,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,088評(píng)論 3 328
  • 文/蒙蒙 一爽锥、第九天 我趴在偏房一處隱蔽的房頂上張望涌韩。 院中可真熱鬧,春花似錦救恨、人聲如沸贸辈。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,716評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至奢啥,卻和暖如春秸仙,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背桩盲。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,857評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工寂纪, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人赌结。 一個(gè)月前我還...
    沈念sama閱讀 47,876評(píng)論 2 370
  • 正文 我出身青樓捞蛋,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親柬姚。 傳聞我的和親對(duì)象是個(gè)殘疾皇子拟杉,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,700評(píng)論 2 354

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