Runtime源码解析-类的加载之_objc_init

Runtime源码解析-类的加载之_objc_init

前言

  • 在app启动后,会把可执行文件加载到内存中。苹果是用过dyld它是一个动态链接器,用来链接库。
  • 到底dyld做了些什么,该文章不做具体讲解。后面在进行启动分析时,具体讲解。
  • 此篇文章我们着重研究类是如何加载到内存中,这里提前剧透,我们通过libobjc源码中_objc_init为入口,进行研究。

_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();
    tls_init();
    static_init();
    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
}
  • 该方法中做了一些初始化操作
    1. environ_init:初始化一些环境变量
    2. tls_init:关于线程key的绑定
    3. static_init:运行C++静态构造函数
    4. runtime_initruntime运行时环境初始化,主要是初始化unattachedCategoriesallocatedClasses两张表
    5. exception_init:初始化libobjc库的异常处理系统
    6. cache_t::init():初始化全局缓存
    7. _imp_implementationWithBlock_init:回调机制,一般情况下不会调用
    8. _dyld_objc_notify_registerdyld注册回调,这是该文章的重点,类的加载和此有关

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);
        }
    }
}
  • PrintHelpPrintOptions任何为true情况下,可以打印环境变量。
  • 这些环境变量会在我们进行调试时提供帮助。

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
}
  • 线程key的绑定,已经本地线程池的初始化

static_init

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();
    }
}
  • 运行系统级别的C++静态构造函数

runtime_init

void runtime_init(void)
{   
    // 分类表的初始化
    objc::unattachedCategories.init(32);
    // 类表的初始化
    objc::allocatedClasses.init();
}
  • 主要是初始化unattachedCategoriesallocatedClasses两张表

exception_init

void exception_init(void)
{
    old_terminate = std::set_terminate(&_objc_terminate);
}
  • 初始化libobjc的异常处理系统。当应用出现crash,系统会发出异常信号,然后会调用_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)();
        }
    }
}
  • 该方法中,回调用uncaught_handler方法抛出异常。全局搜索该方法
objc_uncaught_exception_handler 
objc_setUncaughtExceptionHandler(objc_uncaught_exception_handler fn)
{
    objc_uncaught_exception_handler result = uncaught_handler;
    uncaught_handler = fn;
    return result;
}
  • 在应用层可以通过调用这个方法,传入一个fn,这样就可以接收到底层内部异常,可以自定义处理异常消息

cache_t::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

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

  • 该方法在libobjc中没有实现,在libdyld中实现了
void _dyld_objc_notify_register(_dyld_objc_notify_mapped    mapped,
                                _dyld_objc_notify_init      init,
                                _dyld_objc_notify_unmapped  unmapped)
{
    dyld::registerObjCNotifiers(mapped, init, unmapped);
}
void registerObjCNotifiers(_dyld_objc_notify_mapped mapped, _dyld_objc_notify_init init, _dyld_objc_notify_unmapped unmapped)
{
    // record functions to call
    sNotifyObjCMapped   = mapped;
    sNotifyObjCInit     = init;
    sNotifyObjCUnmapped = unmapped;

    // call 'mapped' function with all images mapped so far
    try {
        notifyBatchPartial(dyld_image_state_bound, true, NULL, false, true);
    }
    catch (const char* msg) {
        // ignore request to abort during registration
    }

    // <rdar://problem/32209809> call 'init' function on all images already init'ed (below libSystem)
    for (std::vector<ImageLoader*>::iterator it=sAllImages.begin(); it != sAllImages.end(); it++) {
        ImageLoader* image = *it;
        if ( (image->getState() == dyld_image_state_initialized) && image->notifyObjC() ) {
            dyld3::ScopedTimer timer(DBG_DYLD_TIMING_OBJC_INIT, (uint64_t)image->machHeader(), 0, 0);
            (*sNotifyObjCInit)(image->getRealPath(), image->machHeader());
        }
    }
}
  • _dyld_objc_notify_register中有3个参数

    • &map_imagesdyldimage加载到内存中会调用该函数

    • load_imagesdyld初始化所有的image时会调用

    • unmap_image:将image移除时会调用

  • 我们先看一下sNotifyObjCMapped方法(&map_images方法)在何处被调用

static void notifyBatchPartial(dyld_image_states state, bool orLater, dyld_image_state_change_handler onlyHandler, bool preflightOnly, bool onlyObjCMappedNotification)
{
    std::vector<dyld_image_state_change_handler>* handlers = stateToHandlers(state, sBatchHandlers);
    if ( (handlers != NULL) || ((state == dyld_image_state_bound) && (sNotifyObjCMapped != NULL)) ) {
        // don't use a vector because it will use malloc/free and we want notifcation to be low cost
        allImagesLock();
        dyld_image_info infos[allImagesCount()+1];
        ImageLoader* images[allImagesCount()+1];
        ImageLoader** end = images;
        for (std::vector<ImageLoader*>::iterator it=sAllImages.begin(); it != sAllImages.end(); it++) {...}
        if ( sBundleBeingLoaded != NULL ) {...}
        const char* dontLoadReason = NULL;
        uint32_t imageCount = (uint32_t)(end-images);
        if ( imageCount != 0 ) {...}
    #if SUPPORT_ACCELERATE_TABLES
        if ( sAllCacheImagesProxy != NULL ) {...}
    #endif
        if ( imageCount != 0 ) {
            if ( !onlyObjCMappedNotification ) {...}
            if ( (onlyHandler == NULL) && ((state == dyld_image_state_bound) || (orLater && (dyld_image_state_bound > state))) && (sNotifyObjCMapped != NULL) ) {
                const char* paths[imageCount];
                const mach_header* mhs[imageCount];
                unsigned objcImageCount = 0;
                for (int i=0; i < imageCount; ++i) {...}
                if ( objcImageCount != 0 ) {
                    dyld3::ScopedTimer timer(DBG_DYLD_TIMING_OBJC_MAP, 0, 0, 0);
                    uint64_t t0 = mach_absolute_time();
                    // 此处被调用
                    (*sNotifyObjCMapped)(objcImageCount, paths, mhs);
                    uint64_t t1 = mach_absolute_time();
                    ImageLoader::fgTotalObjCSetupTime += (t1-t0);
                }
            }
        }
        allImagesUnlock();
        if ( dontLoadReason != NULL )
            throw dontLoadReason;
        if ( !preflightOnly && (state == dyld_image_state_dependents_mapped) ) {...}
}
  • sNotifyObjCMapped调用的地方是在notifyBatchPartial方法中。接着搜索notifyBatchPartial被谁调用,发现是在registerObjCNotifiers
void registerObjCNotifiers(_dyld_objc_notify_mapped mapped, _dyld_objc_notify_init init, _dyld_objc_notify_unmapped unmapped)
{
    // record functions to call
    sNotifyObjCMapped   = mapped;
    sNotifyObjCInit     = init;
    sNotifyObjCUnmapped = unmapped;

    // call 'mapped' function with all images mapped so far
    try {
        // 该方法内部,调用sNotifyObjCMapped方法
        notifyBatchPartial(dyld_image_state_bound, true, NULL, false, true);
    }
    catch (const char* msg) {
        // ignore request to abort during registration
    }

    // <rdar://problem/32209809> call 'init' function on all images already init'ed (below libSystem)
    // 接着调用sNotifyObjCInit方法
    for (std::vector<ImageLoader*>::iterator it=sAllImages.begin(); it != sAllImages.end(); it++) {
        ImageLoader* image = *it;
        if ( (image->getState() == dyld_image_state_initialized) && image->notifyObjC() ) {
            dyld3::ScopedTimer timer(DBG_DYLD_TIMING_OBJC_INIT, (uint64_t)image->machHeader(), 0, 0);
            (*sNotifyObjCInit)(image->getRealPath(), image->machHeader());
        }
    }
}
  • registerObjCNotifiers方法中,先调用map_images后调用load_images方法。
  • 下面我们先看看map_images方法,它把给定的镜像文件,映射到内存中。

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.

        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]);
        }
    }
}
  • 该方法主要做了以下几件事:
    • preopt_init:初始化相关环境
    • 计算类的个数
    • _read_images:加载镜像文件
    • loadImageFuncs:调用镜像加载功能
  • 这里最核心的就是,镜像文件是如何被加载的,所以我们进入_read_images方法。该方法内部代码比较复杂,有点无从下手。如果要一点点读很容易陷入细节。发现苹果开发提供了log日志。所以我们先大致看一下该方法做了哪些事。

_read_images

void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int unoptimizedTotalClasses)
{
    // 省略部分代码
    #define EACH_HEADER \
    hIndex = 0;         \
    hIndex < hCount && (hi = hList[hIndex]); \
    hIndex++
    
    // 首次进行初始化
    if (!doneOnce) {...}
    
    // Fix up @selector references
    // 修复编译阶段混乱的@selector
    static size_t UnfixedSelectors;
    {...}
    ts.log("IMAGE TIMES: fix up selector references");

    
    // 修复错误的类
    bool hasDyldRoots = dyld_shared_cache_some_image_overridden();
    for (EACH_HEADER) {...}
    ts.log("IMAGE TIMES: discover classes");
    
    // 重新映射一些类
    if (!noClassesRemapped()) {...}
    ts.log("IMAGE TIMES: remap classes");
    
    
#if SUPPORT_FIXUP
    // 修复一些消息
    for (EACH_HEADER) {...}
    ts.log("IMAGE TIMES: fix up objc_msgSend_fixup");
#endif
    
    // 读取类中协议 readProtocol
    for (EACH_HEADER) {...}
    ts.log("IMAGE TIMES: discover protocols");
    
    // 修复没有加载的协议
    for (EACH_HEADER) {...}
    ts.log("IMAGE TIMES: fix up @protocol references");
    
    // 分类的处理
    if (didInitialAttachCategories) {...}
    ts.log("IMAGE TIMES: discover categories");

    // 类的加载处理
    for (EACH_HEADER) {...}
    ts.log("IMAGE TIMES: realize non-lazy classes");
    
    // 处理一些不需要的类
    if (resolvedFutureClasses) {...}
    ts.log("IMAGE TIMES: realize future classes");
    
    // 省略部分代码
#undef EACH_HEADER
}
  • 该方法通过log信息得知主要做了以下几件事:
    1. 第一次进入一些初始化操作
    2. 修复预编译阶段@selector的错误
    3. 修复错误的类
    4. 重新映射一些类
    5. 修复一些消息
    6. 读取类中协议 readProtocol
    7. 修复没有加载的协议
    8. 分类的处理
    9. 类的加载处理
    10. 处理一些不需要的类
  • 下面我们逐步分析

1. 第一次进入一些初始化操作

if (!doneOnce) {
    doneOnce = YES; // 加载一次后,不会再调用
    launchTime = YES;

    // 省略一下代码

    // 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");
}
  • 加载一次后doneOnce=YES,下次就不会在进入判断。
  • 第一次进来主要创建表gdb_objc_realized_classes,表里用来存放所有的类

2. 修复预编译阶段@selector的错误

static size_t UnfixedSelectors;
{
    mutex_locker_t lock(selLock);
    for (EACH_HEADER) {
        if (hi->hasPreoptimizedSelectors()) continue;

        bool isBundle = hi->isBundle();
        // 从macho文件中获取方法名列表
        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;
            }
        }
    }
}
  • 因为不同类中可能相同的方法,但是虽然是相同的方法但是地址不同,对那些混乱的方法进行修复。因为方法是存放在类中,每个类中的位置是不一样的,所以方法的地址也就不一样

3. 修复错误的类

for (EACH_HEADER) {
    if (! mustReadClasses(hi, hasDyldRoots)) {
        // Image is sufficiently optimized that we need not call readClass()
        continue;
    }
    
    // 从macho中读取类列表信息
    classref_t const *classlist = _getObjc2ClassList(hi, &count);

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

    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;
        }
    }
}
  • _getObjc2ClassList可执行文件machO中获取类列表,对类进行处理
  • newClass处,添加断点

[图片上传失败...(image-be2d62-1684941140727)]

  • cls指向的是一块地址,newCls此时还没有赋值,系统随机给我分配一块脏地址。接着再走一步

[图片上传失败...(image-c89da3-1684941140727)]

  • 图片显示,此时newClscls指向同一块地址。我们看一下readClass具体做了什么
Class readClass(Class cls, bool headerIsBundle, bool headerIsPreoptimized)
{
    // 获取类名
    const char *mangledName = cls->nonlazyMangledName();
    if (missingWeakSuperclass(cls)) { ... }
    cls->fixupBackwardDeployingStableSwift();
    Class replacing = nil;

    if (mangledName != nullptr) { ... }

    if (headerIsPreoptimized  &&  !replacing) {
        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.");
        }
        // 将关联的类插入到另一张哈希表中
        addClassTableEntry(cls);
    }
    // for future reference: shared cache never contains MH_BUNDLEs
    if (headerIsBundle) { ... }
    return cls;
}
  1. 通过cls->nonlazyMangledName()获取类名

  2. addNamedClass把类名和地址关联起来

  3. addClassTableEntry将关联后的类,插入到一张哈希表中

addNamedClass
  • 我们看一下addNamedClass是如何关联起来的
static void addNamedClass(Class cls, const char *name, Class replacing = nil)
{
    runtimeLock.assertLocked();
    Class old;
    if ((old = getClassExceptSomeSwift(name))  &&  old != replacing) {
        inform_duplicate(name, old, cls);

        // getMaybeUnrealizedNonMetaClass uses name lookups.
        // Classes not found by name lookup must be in the
        // secondary meta->nonmeta table.
        addNonMetaClass(cls);
    } else {
        NXMapInsert(gdb_objc_realized_classes, name, cls);
    }
    ASSERT(!(cls->data()->flags & RO_META));
}
  • 根据提示可知,更新gdb_objc_realized_classes哈希表,keynamevaluecls
addClassTableEntry
static void
addClassTableEntry(Class cls, bool addMeta = true)
{
    runtimeLock.assertLocked();

    // This class is allowed to be a known class via the shared cache or via
    // data segments, but it is not allowed to be in the dynamic table already.
    auto &set = objc::allocatedClasses.get();

    ASSERT(set.find(cls) == set.end());

    if (!isKnownClass(cls))
        set.insert(cls);
    if (addMeta)
        addClassTableEntry(cls->ISA(), false);
}
  • allocatedClasses_objc_initruntime_init运行时环境初始化,里面主要是unattachedCategoriesallocatedClasses两张表。此处是把cls插入allocatedClasses表中
  • 如果addMeta = true 将元类添加allocatedClasses表中。

4. 重新映射一些类

// 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]);
        }
    }
}
  • 主要是将未映射的ClassSuper Class进行重新映射:

    • _getObjc2ClassRefs用来获取MachO中静态段__objc_classrefs,即获取类的引用;

    • _getObjc2SuperRefs用来获取MachO中静态段__objc_superrefs,即获取父类的引用;

5. 修复一些消息

#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
  • 通过_getObjc2MessageRefs:获取MachO的静态段__objc_msgrefs
  • fixupMessageRef:将函数指针进行注册,并且对于需要特定指针进行修复

6. 读取类中协议 readProtocol

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

    if (launchTime && isPreoptimized) {
        if (PrintProtocols) {
            _objc_inform("PROTOCOLS: Skipping reading protocols in image: %s",
                         hi->fname());
        }
        continue;
    }

    bool isBundle = hi->isBundle();

    protocol_t * const *protolist = _getObjc2ProtocolList(hi, &count);
    for (i = 0; i < count; i++) {
        readProtocol(protolist[i], cls, protocol_map, 
                     isPreoptimized, isBundle);
    }
}
  • Class cls = (Class)&OBJC_CLASS_$_Protocol;:查找cls = Protocol
  • NXMapTable *protocol_map = protocols();:创建协议的哈希表
  • 通过_getObjc2ProtocolList(hi, &count);获取到MachO中的静态段__objc_protolist协议列表
  • readProtocol:通过该方法把协议添加到protocol_map

7. 修复没有加载的协议

for (EACH_HEADER) {
    // At launch time, we know preoptimized image refs are pointing at the
    // shared cache definition of a protocol.  We can skip the check on
    // launch, but have to visit @protocol refs for shared cache images
    // loaded later.
    if (launchTime && hi->isPreoptimized())
        continue;
    protocol_t **protolist = _getObjc2ProtocolRefs(hi, &count);
    for (i = 0; i < count; i++) {
        remapProtocolRef(&protolist[i]);
    }
}
  • _getObjc2ProtocolRefs:获取到MachO的静态段 __objc_protorefs
  • remapProtocolRef:比较当前协议和协议列表中的同一个内存地址的协议是否相同,如果不同则替换

8. 分类的处理

if (didInitialAttachCategories) {
    for (EACH_HEADER) {
        load_categories_nolock(hi);
    }
}
  • 主要用来处理分类,我们在分类加载篇章详细介绍

9. 类的加载处理

for (EACH_HEADER) {
    classref_t const *classlist = hi->nlclslist(&count);
    for (i = 0; i < count; i++) {
        Class cls = remapClass(classlist[i]);
        if (!cls) continue;

        addClassTableEntry(cls);

        if (cls->isSwiftStable()) {
            if (cls->swiftMetadataInitializer()) {
                _objc_fatal("Swift class %s with a metadata initializer "
                            "is not allowed to be non-lazy",
                            cls->nameForLogging());
            }
            // fixme also disallow relocatable classes
            // We can't disallow all Swift classes because of
            // classes like Swift.__EmptyArrayStorage
        }
        realizeClassWithoutSwift(cls, nil);
    }
}
  • 主要处理主类,我们在类的加载篇章详细介绍

10. 处理一些不需要的类

if (resolvedFutureClasses) {
    for (i = 0; i < resolvedFutureClassCount; i++) {
        Class cls = resolvedFutureClasses[i];
        if (cls->isSwiftStable()) {
            _objc_fatal("Swift class is not allowed to be future");
        }
        realizeClassWithoutSwift(cls, nil);
        cls->setInstancesRequireRawIsaRecursively(false/*inherited*/);
    }
    free(resolvedFutureClasses);
}
  • 处理被删除,或者移动后的类(未来类)

load_images

  • 在上一个节,我们通过分析map_images大概了解,如何把镜像文件映射到内存中。
  • 接着我们查看load_images它做了什么
void
load_images(const char *path __unused, const struct mach_header *mh)
{
    if (!didInitialAttachCategories && didCallDyldNotifyRegister) {
        didInitialAttachCategories = true;
        // 加载所有的分类
        loadAllCategories();
    }

    // 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
    {
        mutex_locker_t lock2(runtimeLock);
        // 准备load方法
        prepare_load_methods((const headerType *)mh);
    }

    // Call +load methods (without runtimeLock - re-entrant)
    // 调用load方法
    call_load_methods();
}
  • loadAllCategories() 加载所有的Category
  • prepare_load_methods((const headerType *)mh) 准备load方法
  • call_load_methods() 调用load方法

loadAllCategories

static void loadAllCategories() {
    mutex_locker_t lock(runtimeLock);

    for (auto *hi = FirstHeader; hi != NULL; hi = hi->getNext()) {
        load_categories_nolock(hi);
    }
}
  • 循环遍历,调用所有的分类

prepare_load_methods

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

    runtimeLock.assertLocked();
    
    // 获取所有懒加载的类
    classref_t const *classlist = 
        _getObjc2NonlazyClassList(mhdr, &count);
    for (i = 0; i < count; i++) {
        // 调用当前类的load方法
        schedule_class_load(remapClass(classlist[i]));
    }
    
    // 获取对应的分类
    category_t * const *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
        if (cls->isSwiftStable()) {
            _objc_fatal("Swift class extensions and categories on Swift "
                        "classes are not allowed to have +load methods");
        }
        // 实现当前类
        realizeClassWithoutSwift(cls, nil);
        ASSERT(cls->ISA()->isRealized());
        // 添加当前的load方法
        add_category_to_loadable_list(cat);
    }
}

call_load_methods

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
        // 递归调用load方法
        while (loadable_classes_used > 0) {
            call_class_loads();
        }

        // 2. Call category +loads ONCE
        // 调用分类的load方法
        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_class_loads调用所有主类的load方法
static void call_class_loads(void)
{
    int i;
    
    // Detach current loadable list.
    struct loadable_class *classes = loadable_classes;
    int used = loadable_classes_used;
    loadable_classes = nil;
    loadable_classes_allocated = 0;
    loadable_classes_used = 0;
    
    // Call all +loads for the detached list.
    for (i = 0; i < used; i++) {
        Class cls = classes[i].cls;
        // 取出对应的load方法
        load_method_t load_method = (load_method_t)classes[i].method;
        if (!cls) continue; 

        if (PrintLoading) {
            _objc_inform("LOAD: +[%s load]\n", cls->nameForLogging());
        }
        // 调用load方法
        (*load_method)(cls, @selector(load));
    }
    
    // Destroy the detached list.
    if (classes) free(classes);
}
  • 通过call_category_loads调用分类的load方法
static bool call_category_loads(void)
{
    int i, shift;
    bool new_categories_added = NO;
    
    // Detach current loadable list.
    struct loadable_category *cats = loadable_categories;
    int used = loadable_categories_used;
    int allocated = loadable_categories_allocated;
    loadable_categories = nil;
    loadable_categories_allocated = 0;
    loadable_categories_used = 0;

    // Call all +loads for the detached list.
    for (i = 0; i < used; i++) {
        Category cat = cats[i].cat;
        load_method_t load_method = (load_method_t)cats[i].method;
        Class cls;
        if (!cat) continue;

        cls = _category_getClass(cat);
        if (cls  &&  cls->isLoadable()) {
            if (PrintLoading) {
                _objc_inform("LOAD: +[%s(%s) load]\n", 
                             cls->nameForLogging(), 
                             _category_getName(cat));
            }
            // 调用load方法
            (*load_method)(cls, @selector(load));
            cats[i].cat = nil;
        }
    }

    // 省略部分方法
    
    return new_categories_added;
}

  • 通过上面代码,可以得出以下结论:类和分类都会调用load方法
    • 类的load比分类的load方法先调用,类中load方法调用完才开始调用分类的load方法
    • 类中的load方法按编译先后顺序,谁先编译谁的load方法先调用
    • 分类中的的load方法按编译先后顺序,谁先编译谁的load方法先调用
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342

推荐阅读更多精彩内容