iOS底层-类的加载(上)

473 阅读6分钟

上一篇我们主要探索了dyld的链接加载,本篇开始我们探索运行时类的加载过程,本篇只是引子。

_objc_init

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();
    //关于线程key的绑定
    tls_init();
    //运行C ++静态构造函数
    static_init();
    //runtime运行时环境初始化
    runtime_init();
    //初始化libobjc的异常处理系统
    exception_init();
#if __OBJC2__
    //缓存条件初始化
    cache_t::init();
#endif
    //启动回调机制
    _imp_implementationWithBlock_init();
    //dyld 注册的地方
    //map_images:dyld将image镜像文件加载进内存时,会触发该函数
    //load_images:dyld初始化image会触发该函数
    //unmap_image:dyld将image移除时会触发该函数
    _dyld_objc_notify_register(&map_images, load_images, unmap_image);
#if __OBJC2__
    didCallDyldNotifyRegister = true;
#endif
}

我们可以看到,_objc_init主要是执行一些初始化方法,包括

  • environ_init():读取影响运行时的环境变量,如果需要可以打印环境变量提供帮助。
  • tls_init():关于线程key的绑定,例如每线程数据的析构函数。
  • static_init():运行C++静态构造函数。
  • runtime_init():runtime运行时环境的初始化,后面我们详细分析。
  • exception_init():初始化libobjc的异常处理系统。
  • cache_t::init():缓存条件初始化。
  • _imp_implementationWithBlock_init():启动回调机制。
  • _dyld_objc_notify_register:dyld的注册。

environ_init 环境变量初始化

environ_init的源码如下:

void environ_init(void) 
{
   ///省略代码
   // Print OBJC_HELP and OBJC_PRINT_OPTIONS output.
    if (PrintHelp  ||  PrintOptions) {
        if (PrintHelp) {
            _objc_inform("Objective-C runtime debugging. Set variable=YES to enable.");
            _objc_inform("OBJC_HELP: describe available environment variables");
            if (PrintOptions) {
                _objc_inform("OBJC_HELP is set");
            }
            _objc_inform("OBJC_PRINT_OPTIONS: list which options are set");
        }
        if (PrintOptions) {
            _objc_inform("OBJC_PRINT_OPTIONS is set");
        }
        ///这里如果满足条件 会打印所有的环境变量
        for (size_t i = 0; i < sizeof(Settings)/sizeof(Settings[0]); i++) {
            const option_t *opt = &Settings[i];            
            if (PrintHelp) _objc_inform("%s: %s", opt->env, opt->help);
            if (PrintOptions && *opt->var) _objc_inform("%s is set", opt->env);
        }
    }
}

可以看出来就是一些环境变量的初始化,参看这段代码,我们可以打印环境变量。

  • 将条件去掉直接调用for循环

    for (size_t i = 0; i < sizeof(Settings)/sizeof(Settings[0]); i++) {
         const option_t *opt = &Settings[i];
         _objc_inform("%s: %s", opt->env, opt->help);
         _objc_inform("%s is set", opt->env);
     }
    
  • 通过终端命令export OBJC_hrlp = 1,打印环境变量

我们平时项目中可能会有几个环境变量可以在我们的xcode配置(target -- Edit Scheme -- Run --Arguments -- Environment Variables)一下对应的值,达到修改环境变量的目的:

  • DYLD_PRINT_STATISTICS:设置 DYLD_PRINT_STATISTICSYES,控制台就会打印 App 的加载时长,包括整体加载时长和动态库加载时长,即main函数之前的启动时间(查看pre-main耗时),可以通过设置了解其耗时部分,这个我们做启动优化会用到。
  • OBJC_DISABLE_NONPOINTER_ISA:不使用nonpointer isa(nonpointer isa指针地址 末尾为1 ),生成的都是普通的isa,这个我们项目里一般不会改,探索源码的时候可以尝试查看两种isa结构的区别。
  • OBJC_PRINT_LOAD_METHODS:打印 ClassCategory+ (void)load 方法的调用信息,启动优化也可以参考,因为load方法过多也会使启动变慢。

tls_init:线程key的绑定

主要作用是本地线程池的初始化以及析构,源码:

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
}

static_init 运行系统级别的C++静态构造函数

主要是运行系统级别的C++静态构造函数,在dyld调用我们的静态构造函数之前,libc调用_objc_init方法,即系统级别的C++构造函数 先于 自定义的C++构造函数 运行

static void static_init()
{
    size_t count;
    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 运行时环境初始化

主要是运行时的初始化,主要分为两部分:分类初始化类的表初始化,我们下一篇会详细探索。

void runtime_init(void)
{
    objc::unattachedCategories.init(32);
    objc::allocatedClasses.init();
}

exception_init 异常初始化

主要是初始化libobjc的异常处理系统,注册异常处理的回调,从而监控异常的处理

/***********************************************************************
* exception_init
* Initialize libobjc's exception handling system.
* Called by map_images().
**********************************************************************/
void exception_init(void)
{
    old_terminate = std::set_terminate(&_objc_terminate);
}
  • _objc_terminate的实现:
static void _objc_terminate(void)
{
    if (PrintExceptions) {
        _objc_inform("EXCEPTIONS: terminating");
    }
    if (! __cxa_current_exception_type()) {
        // No current exception.
        (*old_terminate)();
    }
    else {
        // There is a current exception. Check if it's an objc exception.
        @try {
            __cxa_rethrow();
        } @catch (id e) {
            // It's an objc object. Call Foundation's handler, if any.
            (*uncaught_handler)((id)e);
            (*old_terminate)();
        } @catch (...) {
            // It's not an objc object. Continue to C++ terminate.
            (*old_terminate)();
        }
    }
}

crash的时候会执行_objc_terminate方法,最后会执行uncaught_handler抛出异常。

  • uncaught_handler:
  • objc_uncaught_exception_handler 
    objc_setUncaughtExceptionHandler(objc_uncaught_exception_handler fn)
    {
    //    fn为设置的异常句柄 传入的函数,为外界给的
        objc_uncaught_exception_handler result = uncaught_handler;
        uncaught_handler = fn; //赋值
        return result;
    }
    
  • 主要用来处理异常,在我们的App中可以添加一个异常句柄NSSetUncaughtExceptionHandler来处理异常。

cache_init:缓存初始化

主要就是缓存初始化

void cache_t::init()
{
#if HAVE_TASK_RESTARTABLE_RANGES
    mach_msg_type_number_t count = 0;
    kern_return_t kr;
​
    while (objc_restartableRanges[count].location) {
        count++;
    }
​
    kr = task_restartable_ranges_register(mach_task_self(),
                                          objc_restartableRanges, count);
    if (kr == KERN_SUCCESS) return;
    _objc_fatal("task_restartable_ranges_register failed (result 0x%x: %s)",
                kr, mach_error_string(kr));
#endif // HAVE_TASK_RESTARTABLE_RANGES
}

_imp_implementationWithBlock_init:启动回调机制

该方法主要是启动回调机制,看代码iOS端没做任何处理。

void
_imp_implementationWithBlock_init(void)
{
#if TARGET_OS_OSX
    // Eagerly load libobjc-trampolines.dylib in certain processes. Some
    // programs (most notably QtWebEngineProcess used by older versions of
    // embedded Chromium) enable a highly restrictive sandbox profile which
    // blocks access to that dylib. If anything calls
    // imp_implementationWithBlock (as AppKit has started doing) then we'll
    // crash trying to load it. Loading it here sets it up before the sandbox
    // profile is enabled and blocks it.
    //
    // This fixes EA Origin (rdar://problem/50813789)
    // and Steam (rdar://problem/55286131)
    if (__progname &&
        (strcmp(__progname, "QtWebEngineProcess") == 0 ||
         strcmp(__progname, "Steam Helper") == 0)) {
        Trampolines.Initialize();
    }
#endif
}

_dyld_objc_notify_register:dyld注册

这个方法我们上一篇有提到过,主要是

  • 仅供objc运行时使用

  • 注册处理程序,以便在映射、取消映射和初始化objc图像时调用

  • dyld将会通过一个包含objc-image-info的镜像文件的数组回调mapped函数

三个参数的作用:

map_images:dyld将image(镜像文件)加载进内存时,会触发该函数

load_image:dyld初始化image会触发该函数

unmap_image:dyld将image移除时,会触发该函数

我们_objc_init调用的方法有了初步认识,接下来我们开始探索类的加载。

_read_images

_read_images的引入

_dyld_objc_notify_register第一个参数是&map_images,所以我们从map_images开始探索。

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比较简单,调用map_images_nolock,我们继续探索。

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__
                // 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;
                  _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_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
    }
    if (hCount > 0) {
       ///核心方法
        _read_images(hList, hCount, totalClasses, unoptimizedTotalClasses);
    }
​
    firstTime = NO;
    
    // Call image load funcs after everything is set up.
    for (auto func : loadImageFuncs) {
        for (uint32_t i = 0; i < mhCount; i++) {
            func(mhdrs[i]);
        }
    }
}

map_images_nolock方法比较长,很大一部分代码是读取镜像的准备,核心调用是_read_images方法。

_read_images分析

_read_images有三百多行代码,一行行读容易迷失,我们先将方法内的{}语句隐藏,从全局看这个方法都做了什么:

截屏2021-07-17 12.56.13.png 这个方法有个特点就是没执行完一个代码块都会执行ts.log()函数打印代码段都做了什么,这样子我们大体知道_read_images的主流程:

  • 条件控制进⾏⼀次的加载
  • 修复预编译阶段的 @selector 的混乱问题
  • 错误混乱的类处理
  • 修复重映射⼀些没有被镜像⽂件加载进来的类
  • 修复⼀些消息
  • 当我们类⾥⾯有协议的时候 : readProtocol
  • 修复没有被加载的协议
  • 分类处理
  • 类的加载处理
  • 没有被处理的类 优化那些被侵犯的类

first time tasks即:条件控制进⾏⼀次的加载

if (!doneOnce) {
        doneOnce = YES;
        launchTime = 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()->swiftUnstableVersion() < 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_program_sdk_at_least(dyld_platform_version_macOS_10_11)) {
//            DisableNonpointerIsa = true;
//            if (PrintRawIsa) {
//                _objc_inform("RAW ISA: disabling non-pointer isa because "
//                             "the app is too old.");
//            }
//        }
​
        // 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();
        }
        ///初始化TaggedPointer混淆
        initializeTaggedPointerObfuscator();
​
        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
        // objc::unattachedCategories.init(32);
        // objc::allocatedClasses.init();
        //负载因子
        int namedClassesSize = 
            (isPreoptimized() ? unoptimizedTotalClasses : totalClasses) * 4 / 3;
        //创建一张类的总表,包含所有的类
        gdb_objc_realized_classes =
            NXCreateMapTable(NXStrValueMapPrototype, namedClassesSize);
        ts.log("IMAGE TIMES: first time tasks");
    }
  • 初始化小对象(TaggedPointer)
  • 创建所有类的总表,注意这个表和runtime_initallocatedClassess的表不一样,这里是所有类的表,而allocatedClassess是已经实现(allocated)的类的表。

fix up selector references即:修复预编译阶段的 @selector 的混乱问题

    static size_t UnfixedSelectors;
    {
        mutex_locker_t lock(selLock);
        for (EACH_HEADER) {
            if (hi->hasPreoptimizedSelectors()) 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]);
                SEL sel = sel_registerNameNoLock(name, isBundle);
                if (sels[i] != sel) {
                    sels[i] = sel;
                }
            }
        }
    }
    ts.log("IMAGE TIMES: fix up selector references");

selecotr是类的名字+地址。

  • SEL *sels = _getObjc2SelectorRefs(hi, &count);是从Mach-o文件读取的
  • SEL sel = sel_registerNameNoLock(name, isBundle);是从dyld链接之后获取的
  • 如果两个sel名字相等,地址不同就需要fix up

discover classes即:错误混乱的类处理

    // Discover classes. Fix up unresolved future classes. Mark bundle classes.
    bool hasDyldRoots = dyld_shared_cache_some_image_overridden();
​
    for (EACH_HEADER) {
        if (! mustReadClasses(hi, hasDyldRoots)) {
            // Image is sufficiently optimized that we need not call readClass()
            continue;
        }
        //从mach-o读取类
        classref_t const *classlist = _getObjc2ClassList(hi, &count);
​
        bool headerIsBundle = hi->isBundle();
        bool headerIsPreoptimized = hi->hasPreoptimizedClasses();
​
        for (i = 0; i < count; i++) {
            //cls 目前没有名字
            Class cls = (Class)classlist[i];
            //关联类cls的名字
            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");

修复未处理的将来的类。给类关联上名字。下面我们重点探索一下readClass:

readClass

Class readClass(Class cls, bool headerIsBundle, bool headerIsPreoptimized)
{
    ///以下代码 为笔者添加 方便调试到自定义的类(因为所以类都会执行这里,我们只关心我们定义的类)
    const char *mangledName = cls->nonlazyMangledName();
    const char *customerClassName = "JSPerson";
    if (strcmp(mangledName, customerClassName) == 0) {
        //打印类名
        printf("%s -: 要研究的类: - %s\n",__func__,mangledName);
    }
    
    if (missingWeakSuperclass(cls)) {
        // No superclass (probably weak-linked). 
        // Disavow any knowledge of this subclass.
        if (PrintConnecting) {
            _objc_inform("CLASS: IGNORING class '%s' with "
                         "missing weak-linked superclass", 
                         cls->nameForLogging());
        }
        addRemappedClass(cls, nil);
        cls->setSuperclass(nil);
        return nil;
    }
    
    cls->fixupBackwardDeployingStableSwift();
​
    Class replacing = nil;
    if (mangledName != nullptr) {
        if (Class newCls = popFutureNamedClass(mangledName)) {
            //断点调试 这里并没有执行
            // This name was previously allocated as a future class.
            // Copy objc_class to future class's struct.
            // Preserve future's rw data block.
​
            if (newCls->isAnySwift()) {
                _objc_fatal("Can't complete future class request for '%s' "
                            "because the real class is too big.",
                            cls->nameForLogging());
            }
​
            class_rw_t *rw = newCls->data();
            const class_ro_t *old_ro = rw->ro();
            memcpy(newCls, cls, sizeof(objc_class));
​
            // Manually set address-discriminated ptrauthed fields
            // so that newCls gets the correct signatures.
            newCls->setSuperclass(cls->getSuperclass());
            newCls->initIsa(cls->getIsa());
​
            rw->set_ro((class_ro_t *)newCls->data());
            newCls->setData(rw);
            freeIfMutable((char *)old_ro->getName());
            free((void *)old_ro);
​
            addRemappedClass(cls, newCls);
​
            replacing = cls;
            cls = newCls;
        }
    }
    if (headerIsPreoptimized  &&  !replacing) {
        // class list built in shared cache
        // fixme strict assert doesn't work because of duplicates
        // ASSERT(cls == getClass(name));
        ASSERT(mangledName == nullptr || getClassExceptSomeSwift(mangledName));
    } else {
        if (mangledName) { //some Swift generic classes can lazily generate their names
            ///给类添加名字
            addNamedClass(cls, mangledName, replacing);
        } else {
            ///元类也处理
            Class meta = cls->ISA();
            const class_ro_t *metaRO = meta->bits.safe_ro();
            ASSERT(metaRO->getNonMetaclass() && "Metaclass with lazy name must have a pointer to the corresponding nonmetaclass.");
            ASSERT(metaRO->getNonMetaclass() == cls && "Metaclass nonmetaclass pointer must equal the original class.");
        }
        ///添加到class表
        addClassTableEntry(cls);
    }
    // for future reference: shared cache never contains MH_BUNDLEs
    if (headerIsBundle) {
        cls->data()->flags |= RO_FROM_BUNDLE;
        cls->ISA()->data()->flags |= RO_FROM_BUNDLE;
    }
    return cls;
}

我们从返回值开始看cls,cls也是入参,经过readClass之后有了名字,主要作用就是对类及元类赋值名字并放入方法表中。

总结

本篇我们主要是对objc_init的流程做了一个简单的梳理,分析了readClass方法的作用,下一篇我们开始详细分析类的加载过程。