[OC学习笔记]启动流程(objc部分)

Posted Billy Miracle

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[OC学习笔记]启动流程(objc部分)相关的知识,希望对你有一定的参考价值。

先回顾下这张图,回顾下整体流程。现在分析下在此流程中objc4源码(818.2)的处理逻辑。

_objc_init解析

我们在上图可以看出,dyld在main函数之前(pre-main)会间接调用到objc_objc_init,其中使用_dyld_objc_notify_register注册了3个方法,但在这之前还做了一些初始化的操作。

/***********************************************************************
* _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();
    //runtime准备,创建2张表
    runtime_init();
    //异常初始化
    exception_init();
#if __OBJC2__
    //缓存
    cache_t::init();
#endif
    _imp_implementationWithBlock_init();

    _dyld_objc_notify_register(&map_images, load_images, unmap_image);

#if __OBJC2__
    didCallDyldNotifyRegister = true;
#endif

environ_init

其中environ_init是读取环境变量(environment variables)的一些配置信息,environment variablesProduct->Scheme->Edit Scheme->Run->Argments->Environment Variables中配置。设置相关信息可以打印一些信息。可以设置OBJC_HELP,启动程序后会打印所有可以设置的信息已经含义解释。

输出:

objc[2763]: OBJC_HELP: describe available environment variables//描述可用的环境变量
objc[2763]: OBJC_HELP is set
objc[2763]: OBJC_PRINT_OPTIONS: list which options are set//列出设置的选项
objc[2763]: OBJC_PRINT_OPTIONS is set
objc[2763]: OBJC_PRINT_IMAGES: log image and library names as they are loaded//在加载时输出image和library名称
objc[2763]: OBJC_PRINT_IMAGE_TIMES: measure duration of image loading steps
objc[2763]: OBJC_PRINT_LOAD_METHODS: log calls to class and category +load methods//打印Class及Category的+(void)load 方法的调用信息
objc[2763]: OBJC_PRINT_INITIALIZE_METHODS: log calls to class +initialize methods//打印Class的+(void)initialize的调用信息
objc[2763]: OBJC_PRINT_RESOLVED_METHODS: log methods created by +resolveClassMethod: and +resolveInstanceMethod://打印通过+resolveClassMethod:和+resolveInstanceMethod:生成的类方法
objc[2763]: OBJC_PRINT_CLASS_SETUP: log progress of class and category setup//打印Class及Category的设置过程
objc[2763]: OBJC_PRINT_PROTOCOL_SETUP: log progress of protocol setup//打印Protocol的设置过程
objc[2763]: OBJC_PRINT_IVAR_SETUP: log processing of non-fragile ivars//打印lar的设置过程
objc[2763]: OBJC_PRINT_VTABLE_SETUP: log processing of class vtables//打印vtable的设置过程
objc[2763]: OBJC_PRINT_VTABLE_IMAGES: print vtable images showing overridden methods//打印vtable被覆盖的方法
objc[2763]: OBJC_PRINT_CACHE_SETUP: log processing of method caches//打印方法缓存的设置过程
objc[2763]: OBJC_PRINT_FUTURE_CLASSES: log use of future classes for toll-free bridging//打印从CFType无缝转换到NSObject将要使用的类(如CFArrayRef到NSArray *)
objc[2763]: OBJC_PRINT_PREOPTIMIZATION: log preoptimization courtesy of dyld shared cache//打印dyld共享缓存优化前的问候语
objc[2763]: OBJC_PRINT_CXX_CTORS: log calls to C++ ctors and dtors for instance variables//打印类实例中的C++对象的构造与析构调用
objc[2763]: OBJC_PRINT_EXCEPTIONS: log exception handling//打印异常处理
objc[2763]: OBJC_PRINT_EXCEPTION_THROW: log backtrace of every objc_exception_throw()//打印所有异常抛出时的回溯
objc[2763]: OBJC_PRINT_ALT_HANDLERS: log processing of exception alt handlers//打印alt操作异常处理
objc[2763]: OBJC_PRINT_REPLACED_METHODS: log methods replaced by category implementations//打印被Category替换的方法
objc[2763]: OBJC_PRINT_DEPRECATION_WARNINGS: warn about calls to deprecated runtime functions//打印所有过时的方法调用
objc[2763]: OBJC_PRINT_POOL_HIGHWATER: log high-water marks for autorelease pools//打印autoreleasepool高水位警告
objc[2763]: OBJC_PRINT_CUSTOM_CORE: log classes with custom core methods//打印具有自定义核心方法的类
objc[2763]: OBJC_PRINT_CUSTOM_RR: log classes with custom retain/release methods//打印含有未优化的自定义retain/release方法的类
objc[2763]: OBJC_PRINT_CUSTOM_AWZ: log classes with custom allocWithZone methods//打印含有未优化的自定义allocWithzone方法的类
objc[2763]: OBJC_PRINT_RAW_ISA: log classes that require raw pointer isa fields//打印需要访问原始isa指针的类
objc[2763]: OBJC_DEBUG_UNLOAD: warn about poorly-behaving bundles when unloaded//卸载有不良行为的Bundle时打印警告
objc[2763]: OBJC_DEBUG_FRAGILE_SUPERCLASSES: warn about subclasses that may have been broken by subsequent changes to superclasses//当子类可能被对父类的修改破坏时打印警告
objc[2763]: OBJC_DEBUG_NIL_SYNC: warn about @synchronized(nil), which does no synchronization//警告@synchronized(nil)调用,这种情况不会加锁
objc[2763]: OBJC_DEBUG_NONFRAGILE_IVARS: capriciously rearrange non-fragile ivars//打印突发地重新布置non-fragileivars的行为
objc[2763]: OBJC_DEBUG_ALT_HANDLERS: record more info about bad alt handler use//记录更多的alt操作错误信息
objc[2763]: OBJC_DEBUG_MISSING_POOLS: warn about autorelease with no pool in place, which may be a leak//警告没有pool的情況下使用autorelease,可能内存泄漏
objc[2763]: OBJC_DEBUG_POOL_ALLOCATION: halt when autorelease pools are popped out of order, and allow heap debuggers to track autorelease pools//当自动释放池无序弹出时停止,并允许堆调试器跟踪自动释放池
objc[2763]: OBJC_DEBUG_DUPLICATE_CLASSES: halt when multiple classes with the same name are present//当出现类重名时停机
objc[2763]: OBJC_DEBUG_DONT_CRASH: halt the process by exiting instead of crashing//通过退出而不是崩溃来停止进程
objc[2763]: OBJC_DEBUG_POOL_DEPTH: log fault when at least a set number of autorelease pages has been allocated//分配了至少设定数量的自动发布页时打印错误
objc[2763]: OBJC_DEBUG_SCRIBBLE_CACHES: scribble the IMPs in freed method caches//在释放的方法缓存中将IMP改乱
objc[2763]: OBJC_DEBUG_SCAN_WEAK_TABLES: scan the weak references table continuously in the background - set OBJC_DEBUG_SCAN_WEAK_TABLES_INTERVAL_NANOSECONDS to set scanning interval (default 1000000)
objc[2763]: OBJC_DISABLE_VTABLES: disable vtable dispatch//关闭vtable分发
objc[2763]: OBJC_DISABLE_PREOPTIMIZATION: disable preoptimization courtesy of dyld shared cache//关闭avld共享缓存优化前的问候语
objc[2763]: OBJC_DISABLE_TAGGED_POINTERS: disable tagged pointer optimization of NSNumber et al.//关闭NSNumber等的tagged pointer优化
objc[2763]: OBJC_DISABLE_TAG_OBFUSCATION: disable obfuscation of tagged pointers
objc[2763]: OBJC_DISABLE_NONPOINTER_ISA: disable non-pointer isa fields//关闭non-pointer isa字段的访问
objc[2763]: OBJC_DISABLE_INITIALIZE_FORK_SAFETY: disable safety checks for +initialize after fork
objc[2763]: OBJC_DISABLE_FAULTS: disable os faults
objc[2763]: OBJC_DISABLE_PREOPTIMIZED_CACHES: disable preoptimized caches
objc[2763]: OBJC_DISABLE_AUTORELEASE_COALESCING: disable coalescing of autorelease pool pointers
objc[2763]: OBJC_DISABLE_AUTORELEASE_COALESCING_LRU: disable coalescing of autorelease pool pointers using look back N strategy

除此之外,environ_init里面还对其他工程配置进行读取,比如NSZombiesEnabled(僵尸对象检测)。

tls_init

接下来是 tls_inittlsThread Local Store的缩写,线程局部存储主要用于在多线程中,存储和维护一些线程相关的数据,存储的数据会被关联到当前线程中去,并不需要锁来维护。

/***********************************************************************
* tls_init
* 关联线程析构函数
* 当线程销毁,调用以上方法
**********************************************************************/
void tls_init(void)

#if SUPPORT_DIRECT_THREAD_KEYS
    pthread_key_init_np(TLS_DIRECT_KEY, &_objc_pthread_destroyspecific);
#else
    _objc_pthread_key = tls_create(&_objc_pthread_destroyspecific);
#endif

这里其实是给线程添加了析构函数,线程销毁,会调用_objc_pthread_destroyspecific方法。
接下来简单介绍下TLS:我们知道在一个进程中,所有线程是共享同一个地址空间的。所以,如果一个变量是全局的或者是静态的,那么所有线程访问的是同一份,如果某一个线程对其进行了修改,也就会影响到其他所有的线程。不过我们可能并不希望这样,所以更多的推荐用基于堆栈的自动变量或函数参数来访问数据,因为基于堆栈的变量总是和特定的线程相联系的。
不过如果某些时候,我们就是需要依赖全局变量或者静态变量,那有没有办法保证在多线程程序中能访问而不互相影响呢?答案是有的。操作系统帮我们提供了这个功能——TLS线程本地存储。TLS的作用是能将数据和执行的特定的线程联系起来。
实现TLS有两种方法:静态TLS和动态TLS。
更多内容可以参考:博客博客

static_init

接下来是static_init:找到objc库中的所有初始化方法,遍历调用,注意:objc库中的静态构造函数早于load方法,load方法早于我们自己的静态构造函数。

/***********************************************************************
* static_init
* Run C++ static constructor functions.
* libc calls _objc_init() before dyld would call our static constructors, 
* so we have to do it ourselves.
**********************************************************************/
static void static_init()

    size_t count;
    //获取objc库里面所有的静态构造函数
    auto inits = getLibobjcInitializers(&_mh_dylib_header, &count);
    for (size_t i = 0; i < count; i++) 
        inits[i]();
    
    auto offsets = getLibobjcInitializerOffsets(&_mh_dylib_header, &count);
    //遍历调用他们
    for (size_t i = 0; i < count; i++) 
        UnsignedInitializer init(offsets[i]);
        init();
    

runtime_init

接下来是runtime_init

void runtime_init(void)

    //分类加载表
    objc::unattachedCategories.init(32);
    //类的加载表
    objc::allocatedClasses.init();

这里面其实是初始化两张表,以备后边加载类使用,这里可以留意一下这两张表unattachedCategoriesallocatedClasses,后面会再次提及到。

其他

其他的初始化,由于不影响整个流程,所以在这一小节一带而过。
exception_init:初始化异常捕捉相关
cache_t::init:初始化缓存相关
_imp_implementationWithBlock_init: MacOS中,让dyld去加载libobjc-trampolines.dylib这个库。

map_images解析

上一篇文章,我们追踪到了load_images的调用时机,这次我们直接来到他调用的地方,先分析其传入的参数。

三个参数:

参数含义
countimages的数量
paths[]库的路径(数组)
mhdrs[]库对应的mach-header 信息(数组)

再看map_image的源码:

/***********************************************************************
* map_images
* Process the given images which are being mapped in by dyld.
* Calls ABI-agnostic code after taking ABI-specific locks.
*
* Locking: write-locks runtimeLock
**********************************************************************/
void
map_images(unsigned count, const char * const paths[],
           const struct mach_header * const mhdrs[])

    mutex_locker_t lock(runtimeLock);
    return map_images_nolock(count, paths, mhdrs);

可以看到是在线程安全的情况下,调用map_images_nolock函数。下面看一下map_images_nolock

void 
map_images_nolock(unsigned mhCount, const char * const mhPaths[],
                  const struct mach_header * const mhdrs[])

    // 是否是第一次加载
    static bool firstTime = YES;
    // hList 是统计 mhdrs 中的每个 mach_header 对应的 header_info
    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();
    
    // 如果添加OBJC_PRINT_IMAGES环境,打印镜像数量
    // 如:objc[10503]: IMAGES: processing 296 newly-mapped images...
    if (PrintImages) 
        _objc_inform("IMAGES: processing %u newly-mapped images...\\n", mhCount);
    


    // Find all images with Objective-C metadata.
    // 计算 class 的数量。根据总数调整各种表格的大小。
    hCount = 0;

    // Count classes. Size various table based on the total.
    int totalClasses = 0;
    int unoptimizedTotalClasses = 0;
    
        uint32_t i = mhCount;
        while (i--) 
            // 取得指定 image 的 header 指针
            const headerType *mhdr = (const headerType *)mhdrs[i];
            // 以 mdr 构建其 header_info,并添加到全局的 header 列表中(是一个链表,看源码到现在还是第一次看到链表的使用)
            // 且通过 GETSECT(_getObjc2ClassList, classref_t const, "__objc_classlist"); 读取 __objc_classlist 区中的 class 数量添加到 totalClasses 中,
            // 以及未从 dyld shared cache 中找到 mhdr 的 header_info 时,添加 class 的数量到 unoptimizedTotalClasses 中
            auto hi = addHeader(mhdr, mhPaths[i], totalClasses, unoptimizedTotalClasses);
            
            // 这里有两种情况下 hi 为空:
            // 1. mhdr 的 magic 不是既定的 MH_MAGIC、MH_MAGIC_64、MH_CIGAM、MH_CIGAM_64 中的任何一个
            // 2. 从 dyld shared cache 中找到了 mhdr 的 header_info,并且 isLoaded 为 true()
            if (!hi) 
                // no objc data in this entry
                continue;
            
            //如果是可执行文件(我们的代码生成的)MH_EXECUTE就是我们主工程的代码
            if (mhdr->filetype == MH_EXECUTE) 
                // Size some data structures based on main executable's size
                // 根据主要可执行文件的大小调整一些数据结构的大小
#if __OBJC2__
                // If dyld3 optimized the main executable, then there shouldn't
                // be any selrefs needed in the dynamic map so we can just init
                // to a 0 sized map
                if ( !hi->hasPreoptimizedSelectors() ) 
                    size_t count;
                    // 获取 __objc_selrefs 区中的 SEL 的数量
                    _getObjc2SelectorRefs(hi, &count);
                    selrefCount += count;
                    
                    // 获取 __objc_msgrefs 区中的 message 数量
                    _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
            
            //把hi存储起来
            hList[hCount++] = hi;
            
            if (PrintImages) 
                // 打印 image 信息
                _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.
    // 执行 one-time runtime 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.
    // 如果可执行文件不包含 Objective-C 代码但稍后动态加载 Objective-C,则该可执行文件可能不会出现在此 infoList 中。
    if (firstTime) 
        // 初始化函数注册表
        sel_init(selrefCount);
        // 这里的 arr_init 函数超重要,可看到它内部做了三件事
        // 1.自动释放池 AutoreleasePoolPage的初始化
        // 2.SideTablesMap初始化
        // 3.全局关联对象表的初始化
        arr_init();
        /*
         void arr_init(void)
         
             AutoreleasePoolPage::init();
             SideTablesMap.init();
             _objc_associations_init();
             if (DebugScanWeakTables)
                 startWeakTableScan();
         
         */

#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
// 这一段是在较低版本下 DYLD_MACOSX_VERSION_10_13 之前的版本中禁用 +initialize fork safety,大致看看即可
#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_program_sdk_at_least(dyld_platform_version_macOS_10_13)) 
//            DisableInitializeForkSafety = true;
//            if (PrintInitializing) 
//                _objc_inform("INITIALIZE: disabling +initialize fork "
//                             "safety enforcement because the app is "
//                             "too old.)");
//            
//        

        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

    
    //⚠️⚠️关键
    // 以 header_info *hList[mhCount] 数组中收集到的 images 的 header_info 为参,直接进行 image 的读取
    if (hCount > 0) 
        // 读取映射
        _read_images(hList, hCount, totalClasses, unoptimizedTotalClasses);
    
    // 把开始时初始化的静态局部变量 firstTime 置为 NO
    firstTime = NO;
    
    // _read_images 看完再看下面的 loadImageFuncs 函数
    
    // Call image load funcs after everything is set up.
    // 一切设置完毕后调用 image 加载函数。
    for (auto func : loadImageFuncs) 
        for (uint32_t i = 0; i < mhCount; i++) 
            func(mhdrs[i]);
        
    

其中最重要的就是_read_images函数的调用,map_images_nolock上半部分就是对const struct mach_header * const mhdrs[]参数的处理,把数组中的mach_header转换为header_info并存在header_info *hList[mhCount]数组中,并统计totalClassesunoptimizedTotalClasses的数量,然后调用_read_images(hList, hCount, totalClasses, unoptimizedTotalClasses)函数。
下面先看_read_images的源码,发现非常长,下面根据顺序给出几段主要的代码:

准备工作

/***********************************************************************
* _read_images
* Perform initial processing of the headers in the linked 
* list beginning with headerList. 
*
* Called by: map_images_nolock
*
* Locking: runtimeLock acquired by map_images
* 
* 对以 headerList 开头的链接列表中的标头执行初始处理。
* 呼叫者: map_images_nolock
* 锁定:运行时锁定由map_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[OC学习笔记]objc_msgSend:方法快速查找

[OC学习笔记]objc_msgSend:方法慢速查找

[OC学习笔记]启动流程

[OC学习笔记]启动流程

[OC学习笔记]class类结构cache_t

[OC学习笔记]objc_msgSend:动态方法决议和消息转发