美文网首页iOS
Objective-C runtime机制(9)——main函数

Objective-C runtime机制(9)——main函数

作者: 无忘无往 | 来源:发表于2019-01-31 15:45 被阅读9次

    在我们的App代码中,XCode会自动创建一个main.m文件,其中定义了main函数

    image

    这里的main函数是我们整个App的入口,它的调用时机甚至会早于AppDelegate 中的didFinishLaunching回调。

    image

    因此我们会说,main函数是我们App程序的入口点函数

    那么,我们App所运行的第一个函数,真的是main函数吗?如果我们在XCode中设置符号断点void _objc_init(void),则会发现,在进入main函数之前,其实系统还会调用void _objc_init(void) 方法:

    image

    这里的_objc_init方法,实际上是runtime的入口函数。

    void _objc_init(void)
    

    也就是说,在App的main函数之前,系统会首先对App的runtime运行环境,做了一系列的初始化操作

    而这个runtime入口函数,又是被谁调用起来的呢?答案是苹果的动态链接器dyld(the dynamic link editor)。dyld是一个操作系统级的组件,它会负责iOS系统中每个App启动时的环境初始化以及动态库加载到内存等一系列操作。
    在系统内核做好程序准备工作之后,交由dyld负责余下的工作。

    在这里再重申一遍,runtime的入口函数是_objc_init,它是在main函数之前被dyld调用的。而+load()方法,则是在main函数前被_objc_init调用。今天,我们就来看一下,在main函数之前,runtime究竟做了哪些初始化工作。

    Mach-O格式

    在深入了解_objc_init的实现之前,我们需要先了解iOS系统中可执行文件的文件格式:Mach-O格式。关于Mach-O格式,我们在Objective-C runtime机制(前传)——Mach-O格式Objective-C runtime机制(前传2)——Mach-O格式和runtime中以及介绍过。

    我们可以在XCode 工程 product文件夹下找到RuntimeEnter.app工程,用finder打开所在目录,其实RuntimeEnter.app是一个压缩包,用鼠标右键选择show Package Contents ,可以看到下面有这些文件,其中和我们工程同名的可运行程序就是Mach-O格式的可运行文件:


    image

    _objc_init

    我们在iOS App中设置符号断点_objc_init,则在App启动时(进入main函数之前),会进入如下调用堆栈:

    image

    可以看到,其底层是由dyld调用起来的。关于dyld我们不去多说,让我们看一下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方法,我们重点关注

     _dyld_objc_notify_register(&map_images, load_images, unmap_image);
    

    _dyld_objc_notify_register方法注册了对dyld中关于加载images的事件回调:

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

    分别注册了那些事件呢?根据注释,我们可以知道,共注册了三个事件的回调:

    • _dyld_objc_notify_mapped : OC image被加载映射到内存(+load()方法在此时被调用)
    • _dyld_objc_notify_init : OC image被init时
    • _dyld_objc_notify_unmapped : OC image被移除内存时

    以上三个回调类型是用的函数指针,定义为

    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

    当image被dyld加载到内存后,会调用回调_dyld_objc_notify_mapped 。在runtime中,对应的函数是:

    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方法实质上会调用map_images_nolock方法。
    而在map_images_nolock内部,又调用了_read_images方法,其核心是用来读取Mach-O格式文件的runtime相关的section信息,并转化为runtime内部的数据结构

    我们来看一下_read_images 方法的实现:

    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 方法写了很长,其实就是做了一件事,将Mach-O文件的section依次读取,并根据内容初始化runtime的内存结构。

    根据注释,_read_images 方法主要做了下面这些事情:

    1. 是否需要禁用isa优化。这里有三种情况:使用了swift 3.0前的swift代码。OSX版本早于10.11。在OSX系统下,Mach-O的DATA段明确指明了__objc_rawisa(不使用优化的isa)。
    2. __objc_classlist section中读取class list
    3. __objc_classrefs section中读取class 引用的信息,并调用remapClassRef方法来处理。
    4. __objc_selrefs section中读取selector的引用信息,并调用sel_registerNameNoLock方法处理。
    5. __objc_protolist section中读取cls的Protocol信息,并调用readProtocol方法来读取Protocol信息。
    6. __objc_protorefs section中读取protocol的ref信息,并调用remapProtocolRef方法来处理。
    7. __objc_nlclslist section中读取non-lazy class信息,并调用static Class realizeClass(Class cls)方法来实现这些class。realizeClass方法核心是初始化objc_class数据结构,赋予初始值。
    8. __objc_catlist section中读取category信息,并调用addUnattachedCategoryForClass方法来为类或元类添加对应的方法,属性和协议。关于Category,我们在前面的文章中,已经有过相关的讨论。

    OK,以上就是在dyld将image map到内存后,runtime所做的事情:**根据Mach-O相关section中的信息,来初始化runtim的内存结构。**

    _dyld_objc_notify_init

    当dyld要init image的时候,会产生_dyld_objc_notify_init通知。在runime中, 是通过load_images方法做回调响应的。

    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方法其实就是干了一件事,调用Class的+load()方法。

    runtime调用+load()方法分为两步走:

    1. Discover load methods
    2. Call +load methods

    Discover load methods

    在Discover load methods中,会调用prepare_load_methods 来处理+load 方法:

    void prepare_load_methods(const headerType *mhdr)
    {
        size_t count, I;
    
        runtimeLock.assertWriting();
    
        // 添加 class 到 call +load 队列(super class 会在 subclass之前)
        classref_t *classlist = 
            _getObjc2NonlazyClassList(mhdr, &count);
        for (i = 0; i < count; i++) {
            schedule_class_load(remapClass(classlist[i]));
        }
    
        // 添加 category到call +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);
        }
    }
    

    prepare_load_methods 逻辑分为两个部分:

    1. 调用schedule_class_load 来组织class的+load 方法
    2. 调用add_category_to_loadable_list来组织category的+load方法

    schedule_class_load 方法实现如下:

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

    这是一个递归调用,会先把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;
    

    从上面的方法可以看出,每一个定义了+load的类,都会被放到loadable_classes中。

    因此,+load方法并不存在子类重写父类之说。而且父类的+load方法会先于子类调用。

    在来看一下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 方法中,会将所有定义了+load方法的category都放到loadable_categories队列中。

    Call +load methods

    将定义了+load方法的class和category分别放到loadable_classesloadable_categories 队列后,runtime就会依次读取队列中的class和category,并为之调用+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方法调用时机是先于sub class的
    2. class的+load方法调用时机是先于category的

    _dyld_objc_notify_unmapped

    当dyld要将image移除内存时,会发送_dyld_objc_notify_unmapped通知。在runtime中,是用unmap_image方法来响应的。

    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信息的移除。

    总结

    在本篇文章中,我们知道了dyld在main()函数之前,会调用runtime的_objc_init 方法。_objc_init是runtime的入口函数,它会根据Mach-O文件中相关的section信息来初始化runtime内存空间。比如,加载class,protocol,以及附加category到class,调用+load方法等。

    当然,在main()函数前,dyld除了调用_objc_init 外,还会做许多其他的操作。如将动态链接库加载入内存。但这就不属于runtime的范畴了,我们不去深究。

    当dyld将我们App的运行环境都准备好后,dyld 会清理现场,将调用栈回归,调用main()函数,这时候,我们的App就算启动了:


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

    在main()函数被调用前,系统其实已经为我们做了很多的准备工作。就像sunnyxx在其博客中说的:

    孤独的 main 函数,看上去是程序的开始,却是一段精彩的终结

    参考资料

    dyld详解
    iOS 程序 main 函数之前发生了什么

    相关文章

      网友评论

        本文标题:Objective-C runtime机制(9)——main函数

        本文链接:https://www.haomeiwen.com/subject/iczksqtx.html