Objective-C runtime机制(9)——main函数前发生了什么

本文深入探讨了Objective-C应用在main函数执行前的幕后过程,包括Mach-O格式、_objc_init函数、dyld在启动时的角色以及如何调用+load方法。在main函数之前,dyld会调用runtime的_objc_init,初始化内存结构,处理类和Category的加载,并调用+load方法。了解这些细节有助于理解App启动时的运行机制。
摘要由CSDN通过智能技术生成

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

在这里插入图片描述

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

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

那么,我们App所运行的第一个函数,真的是main函数吗?如果我们在XCode中设置符号断点void _objc_init(void),则会发现,在进入main函数之前,其实系统还会调用void _objc_init(void) 方法:
在这里插入图片描述
这里的_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格式的可运行文件:
在这里插入图片描述

_objc_init

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

在这里插入图片描述

可以看到,其底层是由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_w
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值