底层原理-13-类的加载(上)

353 阅读10分钟

之前我们探究了dyld的加载流程,知道了编译的大概意思:把我们写的代码转换机器识别的代码,通过dyld把动态库进行链接最后生成可执行mach-o文件。那么对于我们日常开发中我们创建的类是怎样加载到内存中的,接下来就探索下。

1 _objc_init流程分析

我们之前探究dyld加载流程时在运行所有初始化程序initializeMainExecutable会for循环实例化镜像doInitialization,在此之前libSystem_initializer必须要先执行。经过libdispatch_init进入_os_object_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.1environ_init()分析

void environ_init(void) 
{
    if (issetugid()) {
        // All environment variables are silently ignored when setuid or setgid
        // This includes OBJC_HELP and OBJC_PRINT_OPTIONS themselves.
        return;
    } 

    // Turn off autorelease LRU coalescing by default for apps linked against
    // older SDKs. LRU coalescing can reorder releases and certain older apps
    // are accidentally relying on the ordering.
    // rdar://problem/63886091
//    if (!dyld_program_sdk_at_least(dyld_fall_2020_os_versions))
//        DisableAutoreleaseCoalescingLRU = true;

    bool PrintHelp = false;
    bool PrintOptions = false;
    bool maybeMallocDebugging = false;

    // Scan environ[] directly instead of calling getenv() a lot.
    //直接扫描environ[],而不是大量调用getenv()
    // This optimizes the case where none are set.
    //优化了没有设置任何参数的情况
    for (char **p = *_NSGetEnviron(); *p != nil; p++) {
        if (0 == strncmp(*p, "Malloc", 6)  ||  0 == strncmp(*p, "DYLD", 4)  ||  
            0 == strncmp(*p, "NSZombiesEnabled", 16))
        {
            maybeMallocDebugging = true;
        }

        if (0 != strncmp(*p, "OBJC_", 5)) continue;
        
        if (0 == strncmp(*p, "OBJC_HELP=", 10)) {
            PrintHelp = true;
            continue;
        }
        if (0 == strncmp(*p, "OBJC_PRINT_OPTIONS=", 19)) {
            PrintOptions = true;
            continue;
        }
        
        if (0 == strncmp(*p, "OBJC_DEBUG_POOL_DEPTH=", 22)) {
            SetPageCountWarning(*p + 22);
            continue;
        }

        const char *value = strchr(*p, '=');
        if (!*value) continue;
        value++;
        
        for (size_t i = 0; i < sizeof(Settings)/sizeof(Settings[0]); i++) {
            const option_t *opt = &Settings[i];
            if ((size_t)(value - *p) == 1+opt->envlen  &&  
                0 == strncmp(*p, opt->env, opt->envlen))
            {
                *opt->var = (0 == strcmp(value, "YES"));
                break;
            }
        }
    }

    // Special case: enable some autorelease pool debugging
    // when some malloc debugging is enabled 
    // and OBJC_DEBUG_POOL_ALLOCATION is not set to something other than NO.
    //特殊情况:打开一些自动释放池调试,当一些malloc调试被打开时,并且OBJC_DEBUG_POOL_ALLOCATION没有被设置为NO以外的值
    
    if (maybeMallocDebugging) {
        const char *insert = getenv("DYLD_INSERT_LIBRARIES");
        const char *zombie = getenv("NSZombiesEnabled");
        const char *pooldebug = getenv("OBJC_DEBUG_POOL_ALLOCATION");
        if ((getenv("MallocStackLogging")
             || getenv("MallocStackLoggingNoCompact")
             || (zombie && (*zombie == 'Y' || *zombie == 'y'))
             || (insert && strstr(insert, "libgmalloc")))
            &&
            (!pooldebug || 0 == strcmp(pooldebug, "YES")))
        {
            DebugPoolAllocation = true;
        }
    }

//    if (!os_feature_enabled_simple(objc4, preoptimizedCaches, true)) {
//        DisablePreoptCaches = true;
//    }

    // Print OBJC_HELP and OBJC_PRINT_OPTIONS output.
    //打印输出OBJC_PRINT_OPTIONS
    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);
        }
    }
}

主要是读取影响运行时的环境变量,如果需要,还可以打印环境变量帮助。我们看下有什么可以打印的选项

截屏2021-07-14 上午11.46.33.png 我们把这个判断条件去除,for循环拿到外面,打印一下看下有什么可选的环境变量。

objc[12216]: OBJC_PRINT_IMAGES: log image and library names as they are loaded
objc[12216]: OBJC_PRINT_IMAGES is set
objc[12216]: OBJC_PRINT_IMAGE_TIMES: measure duration of image loading steps
objc[12216]: OBJC_PRINT_IMAGE_TIMES is set
objc[12216]: OBJC_PRINT_LOAD_METHODS: log calls to class and category +load methods
objc[12216]: OBJC_PRINT_LOAD_METHODS is set
objc[12216]: OBJC_PRINT_INITIALIZE_METHODS: log calls to class +initialize methods
objc[12216]: OBJC_PRINT_INITIALIZE_METHODS is set
objc[12216]: OBJC_PRINT_RESOLVED_METHODS: log methods created by +resolveClassMethod: and +resolveInstanceMethod:
objc[12216]: OBJC_PRINT_RESOLVED_METHODS is set
objc[12216]: OBJC_PRINT_CLASS_SETUP: log progress of class and category setup
objc[12216]: OBJC_PRINT_CLASS_SETUP is set
objc[12216]: OBJC_PRINT_PROTOCOL_SETUP: log progress of protocol setup
objc[12216]: OBJC_PRINT_PROTOCOL_SETUP is set
objc[12216]: OBJC_PRINT_IVAR_SETUP: log processing of non-fragile ivars
objc[12216]: OBJC_PRINT_IVAR_SETUP is set
objc[12216]: OBJC_PRINT_VTABLE_SETUP: log processing of class vtables
objc[12216]: OBJC_PRINT_VTABLE_SETUP is set
objc[12216]: OBJC_PRINT_VTABLE_IMAGES: print vtable images showing overridden methods
objc[12216]: OBJC_PRINT_VTABLE_IMAGES is set
objc[12216]: OBJC_PRINT_CACHE_SETUP: log processing of method caches
objc[12216]: OBJC_PRINT_CACHE_SETUP is set
objc[12216]: OBJC_PRINT_FUTURE_CLASSES: log use of future classes for toll-free bridging
objc[12216]: OBJC_PRINT_FUTURE_CLASSES is set
objc[12216]: OBJC_PRINT_PREOPTIMIZATION: log preoptimization courtesy of dyld shared cache
objc[12216]: OBJC_PRINT_PREOPTIMIZATION is set
objc[12216]: OBJC_PRINT_CXX_CTORS: log calls to C++ ctors and dtors for instance variables
objc[12216]: OBJC_PRINT_CXX_CTORS is set
objc[12216]: OBJC_PRINT_EXCEPTIONS: log exception handling
objc[12216]: OBJC_PRINT_EXCEPTIONS is set
objc[12216]: OBJC_PRINT_EXCEPTION_THROW: log backtrace of every objc_exception_throw()
objc[12216]: OBJC_PRINT_EXCEPTION_THROW is set
objc[12216]: OBJC_PRINT_ALT_HANDLERS: log processing of exception alt handlers
objc[12216]: OBJC_PRINT_ALT_HANDLERS is set
objc[12216]: OBJC_PRINT_REPLACED_METHODS: log methods replaced by category implementations
objc[12216]: OBJC_PRINT_REPLACED_METHODS is set
objc[12216]: OBJC_PRINT_DEPRECATION_WARNINGS: warn about calls to deprecated runtime functions
objc[12216]: OBJC_PRINT_DEPRECATION_WARNINGS is set
objc[12216]: OBJC_PRINT_POOL_HIGHWATER: log high-water marks for autorelease pools
objc[12216]: OBJC_PRINT_POOL_HIGHWATER is set
objc[12216]: OBJC_PRINT_CUSTOM_CORE: log classes with custom core methods
objc[12216]: OBJC_PRINT_CUSTOM_CORE is set
objc[12216]: OBJC_PRINT_CUSTOM_RR: log classes with custom retain/release methods
objc[12216]: OBJC_PRINT_CUSTOM_RR is set
objc[12216]: OBJC_PRINT_CUSTOM_AWZ: log classes with custom allocWithZone methods
objc[12216]: OBJC_PRINT_CUSTOM_AWZ is set
objc[12216]: OBJC_PRINT_RAW_ISA: log classes that require raw pointer isa fields
objc[12216]: OBJC_PRINT_RAW_ISA is set
objc[12216]: OBJC_DEBUG_UNLOAD: warn about poorly-behaving bundles when unloaded
objc[12216]: OBJC_DEBUG_UNLOAD is set
objc[12216]: OBJC_DEBUG_FRAGILE_SUPERCLASSES: warn about subclasses that may have been broken by subsequent changes to superclasses
objc[12216]: OBJC_DEBUG_FRAGILE_SUPERCLASSES is set
objc[12216]: OBJC_DEBUG_NIL_SYNC: warn about @synchronized(nil), which does no synchronization
objc[12216]: OBJC_DEBUG_NIL_SYNC is set
objc[12216]: OBJC_DEBUG_NONFRAGILE_IVARS: capriciously rearrange non-fragile ivars
objc[12216]: OBJC_DEBUG_NONFRAGILE_IVARS is set
objc[12216]: OBJC_DEBUG_ALT_HANDLERS: record more info about bad alt handler use
objc[12216]: OBJC_DEBUG_ALT_HANDLERS is set
objc[12216]: OBJC_DEBUG_MISSING_POOLS: warn about autorelease with no pool in place, which may be a leak
objc[12216]: OBJC_DEBUG_MISSING_POOLS is set
objc[12216]: OBJC_DEBUG_POOL_ALLOCATION: halt when autorelease pools are popped out of order, and allow heap debuggers to track autorelease pools
objc[12216]: OBJC_DEBUG_POOL_ALLOCATION is set
objc[12216]: OBJC_DEBUG_DUPLICATE_CLASSES: halt when multiple classes with the same name are present
objc[12216]: OBJC_DEBUG_DUPLICATE_CLASSES is set
objc[12216]: OBJC_DEBUG_DONT_CRASH: halt the process by exiting instead of crashing
objc[12216]: OBJC_DEBUG_DONT_CRASH is set
objc[12216]: OBJC_DEBUG_POOL_DEPTH: log fault when at least a set number of autorelease pages has been allocated
objc[12216]: OBJC_DEBUG_POOL_DEPTH is set
objc[12216]: OBJC_DISABLE_VTABLES: disable vtable dispatch
objc[12216]: OBJC_DISABLE_VTABLES is set
objc[12216]: OBJC_DISABLE_PREOPTIMIZATION: disable preoptimization courtesy of dyld shared cache
objc[12216]: OBJC_DISABLE_PREOPTIMIZATION is set
objc[12216]: OBJC_DISABLE_TAGGED_POINTERS: disable tagged pointer optimization of NSNumber et al.
objc[12216]: OBJC_DISABLE_TAGGED_POINTERS is set
objc[12216]: OBJC_DISABLE_TAG_OBFUSCATION: disable obfuscation of tagged pointers
objc[12216]: OBJC_DISABLE_TAG_OBFUSCATION is set
objc[12216]: OBJC_DISABLE_NONPOINTER_ISA: disable non-pointer isa fields
objc[12216]: OBJC_DISABLE_NONPOINTER_ISA is set
objc[12216]: OBJC_DISABLE_INITIALIZE_FORK_SAFETY: disable safety checks for +initialize after fork
objc[12216]: OBJC_DISABLE_INITIALIZE_FORK_SAFETY is set
objc[12216]: OBJC_DISABLE_FAULTS: disable os faults
objc[12216]: OBJC_DISABLE_FAULTS is set
objc[12216]: OBJC_DISABLE_PREOPTIMIZED_CACHES: disable preoptimized caches
objc[12216]: OBJC_DISABLE_PREOPTIMIZED_CACHES is set
objc[12216]: OBJC_DISABLE_AUTORELEASE_COALESCING: disable coalescing of autorelease pool pointers
objc[12216]: OBJC_DISABLE_AUTORELEASE_COALESCING is set
objc[12216]: OBJC_DISABLE_AUTORELEASE_COALESCING_LRU: disable coalescing of autorelease pool pointers using look back N strategy
objc[12216]: OBJC_DISABLE_AUTORELEASE_COALESCING_LRU is set

我们也可以用终端输出export OBJC_HELP=1

截屏2021-07-14 下午2.46.29.png 我们找2个比较熟悉的操作一下OBJC_DISABLE_NONPOINTER_ISA: disable non-pointer isa fields和OBJC_PRINT_LOAD_METHODS: log calls to class and category +load methods 。是否是纯的isa和load方法打印。

1.1.1 OBJC_DISABLE_NONPOINTER_ISA验证

截屏2021-07-14 下午2.21.44.png 正常情况下我们打印一下实例对象的isa,把他转换成二进制。得到第一位是1说明nonpointer的值为1,不是纯的isa。

截屏2021-07-14 下午2.15.14.png 我们在设置里面设置下OBJC_DISABLE_NONPOINTER_ISAYES,取反。不是不是纯的isa。

截屏2021-07-14 下午2.30.01.png 再次打印首位nonpointer为0,表示是纯的isa

1.1.2 OBJC_PRINT_LOAD_METHODS验证

截屏2021-07-14 下午2.32.33.png 在设置中吧OBJC_PRINT_LOAD_METHODS设置为YES,打印所有+load方法

截屏2021-07-14 下午2.33.43.png 可以在开发中,查找+(load)方法调用,进行一些优化,加快app启动时间。

1.2 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
}
void _objc_pthread_destroyspecific(void *arg)
{
    _objc_pthread_data *data = (_objc_pthread_data *)arg;
    if (data != NULL) {
        _destroyInitializingClassList(data->initializingClasses);
        _destroySyncCache(data->syncCache);
        _destroyAltHandlerList(data->handlerList);
        for (int i = 0; i < (int)countof(data->printableNames); i++) {
            if (data->printableNames[i]) {
                free(data->printableNames[i]);  
            }
        }
        free(data->classNameLookups);

        // add further cleanup here...

        free(data);
    }
}

主要是关于线程key的绑定进行初始化,以及每个线程的析构函数

1.3 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++静态构造函数。libc在dyld调用静态构造函数之前调用_objc_init(), 所以我们必须自己做。之前我们知道在实例化镜像是进入doImageInit会先调用_objc_init.执行完doImageInit后会调用doModInitFunctions也就是运行静态构造函数

1.3 runtime_init()

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

runtime运行时初始化,主要是初始化2个表,一个未依附的类目列表初始化,一个是包含所有已经分配的类和元类列表。

1.4 exception_init()

void exception_init(void)
{
    old_terminate = std::set_terminate(&_objc_terminate);
}

初始化libobjc异常处理系统,由map_images()回调


/***********************************************************************
* _objc_terminate 结束回调
* Custom std::terminate handler.
*
* The uncaught exception callback is implemented as a std::terminate handler.
 未捕获的异常回调被实现为std::terminate处理程序。
* 1. Check if there's an active exception
 检查是否有活动异常
* 2. If so, check if it's an Objective-C exception
 如果是,检查它是否是一个Objective-C异常
* 3. If so, call our registered callback with the object.
 如果是,用该对象调用我们注册的回调函数。
* 4. Finally, call the previous terminate handler.
 最后,调用前面的终止处理程序。
**********************************************************************/
static void (*old_terminate)(void) = nil;
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.
        //检查下是否是oc类的意外
        @try {
            __cxa_rethrow();
        } @catch (id e) {
            // It's an objc object. Call Foundation's handler, if any.
            //是oc对象就调用回调处理,默认回调不处理
            (*uncaught_handler)((id)e);
            (*old_terminate)();
        } @catch (...) {
            // It's not an objc object. Continue to C++ terminate.
            //不是oc对象的话,继续查找C++异常回调处理。oc就不对这个异常处理了
            (*old_terminate)();
        }
    }
}

看下回调处理

/***********************************************************************
* _objc_default_uncaught_exception_handler 默认不会对异常进行处理
* Default uncaught exception handler. Expected to be overridden by Foundation.
**********************************************************************/
static void _objc_default_uncaught_exception_handler(id exception)
{
}
static objc_uncaught_exception_handler uncaught_handler = _objc_default_uncaught_exception_handler;


/***********************************************************************
* objc_setExceptionPreprocessor oc中异常的预处理
* Set a handler for preprocessing Objective-C exceptions. 设置一个回调对于oc中异常的预处理
* Returns the previous handler. 
**********************************************************************/
objc_exception_preprocessor
objc_setExceptionPreprocessor(objc_exception_preprocessor fn)
{
    objc_exception_preprocessor result = exception_preprocessor;
    exception_preprocessor = fn;
    return result;
}

我们可以在代码中实现NSSetUncaughtExceptionHandler(&handleCrash)进行崩溃的处理,获取堆栈信息 ,上传一些崩溃日志等,具体可以看下网上开源库AvoidCrash 说明

1.5 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
}

缓存条件初始化

1.6 _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
}

该方法主要启动回调机制,通常这不会做什么,因为所有的初始化都是惰性的,但是对于某些进程,我们会迫不及待加载trampolines dylib

1.7 _dyld_objc_notify_register

// 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中实现。
_dyld_objc_notify_register(&map_images, load_images, unmap_image)中有3个参数。

  • &map_images:引用传递,dyld在映射image镜像到内存时会触发该函数。使用&是因为映射过程耗时的,确保映射后引用的也是最新的。相当于不使用指针,而是使用指针指向的内存内容。
  • load_images:值传递,dyld加载image的时候会触发该函数。
  • unmap_image: 值传递,dyld移除image的时候会触发该函数。

截屏2021-07-14 下午4.37.59.png

2 _read_images分析

我们看下注册函数中map_images的实现

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);
    //处理dyld映射到的给定镜像。
}

看下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.
    //找到所有镜像中oc元数据
    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]);
        }
    }
}

核心是_read_images读取,我们把功能折叠起来看下具体的实现

image.png 可以根据log注释查看具体的读取步骤。

2.1 first time tasks

  if (DisableTaggedPointers) {
            disableTaggedPointers();//不可用小对象处理
        }
        
        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 加载因子
        int namedClassesSize = 
            (isPreoptimized() ? unoptimizedTotalClasses : totalClasses) * 4 / 3;
        //计算大小 创建空间的时候实际大小是,实际大小 = 创建大小*3/4 那么现在要得到创建大小则 = 实际大小*4/3
        gdb_objc_realized_classes =
            NXCreateMapTable(NXStrValueMapPrototype, namedClassesSize);

        //gdb_objc_realized_classes实际上是一个列表,命名类不在dyld共享缓存中,无论是否实现。这个列表排除了惰性命名的类,这些类必须被查找使用getClass钩子。
        ts.log("IMAGE TIMES: first time tasks");

条件控制完成一次加载

2.2 fix up selector references

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

修复预编译阶段 @selector混乱问题截屏2021-07-14 下午6.13.01.png 2个方法名相同,但是还是进入了判断条件,因为他们指针指向的地址内容不一样imp不一样,所以要统一,避免混乱。== 的含义是指对象不仅指针一样,指针指向的内容也是一样的。

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

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

    ts.log("IMAGE TIMES: discover classes");

查找所有类,修复一些即将要移除的类 标记这些类,错误混乱的类的处理

2.4 remap 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]);
            }
        }
    }

修复一些没有被镜像文件加载进来的类

2.5 objc_msgSend_fixup

 // Fix up old objc_msgSend_fixup call sites修复旧的objc_msgSend_fixup调用站点
    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");

修复一些消息

2.6 discover protocols

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

        // Skip reading protocols if this is an image from the shared cache
        // and we support roots
        // Note, after launch we do need to walk the protocol as the protocol
        // in the shared cache is marked with isCanonical() and that may not
        // be true if some non-shared cache binary was chosen as the canonical
        // definition
        //如果这是来自共享缓存的镜像,则跳过读取协议。在启动后,我们需要遍历协议作为协议,在共享缓存中标记isCanonical(),如果选择非共享缓存二进制文件作为规范,则为true
        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);
        }
    }

    ts.log("IMAGE TIMES: discover protocols");


类里面有协议的话,读取协议readProtocol

2.7 fix up @protocol references

    // Fix up @protocol references 修复协议的引用
    // Preoptimized images may have the right 预优化的镜像可能有权限
    // answer already but we don't know for sure.
    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]);
        }
    }

    ts.log("IMAGE TIMES: fix up @protocol references");

修复没有被加载的协议

2.8 discover categories

  // Discover categories. Only do this after the initial category
    // attachment has been done. For categories present at startup,
    // discovery is deferred until the first load_images call after
    // the call to _dyld_objc_notify_register completes. rdar://problem/53119145
    if (didInitialAttachCategories) {
        for (EACH_HEADER) {
            load_categories_nolock(hi);
        }
    }

    ts.log("IMAGE TIMES: discover categories");

分类处理

2.9 realize non-lazy classes

   // Category discovery MUST BE Late to avoid potential races
    // when other threads call the new category code before
    // this thread finishes its fixups.

    // +load handled by prepare_load_methods()

    // Realize non-lazy classes (for +load methods and static instances)
    //实现非惰性类(用于+加载方法和静态实例)
    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);
        }
    }

    ts.log("IMAGE TIMES: realize non-lazy classes");

类的加载处理

2.10 realize future classes

   // Realize newly-resolved future classes, in case CF manipulates them
    //实现新解析的未来类,以防CF操纵它们
    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);
    }

    ts.log("IMAGE TIMES: realize future classes");

没有被处理的类,优化那些被侵犯的类。

3. realizeClassWithoutSwift(oc类的实现)

static Class realizeClassWithoutSwift(Class cls, Class previously)
{
    runtimeLock.assertLocked();

    class_rw_t *rw;
    Class supercls;
    Class metacls;

    if (!cls) return nil;
    if (cls->isRealized()) {
        validateAlreadyRealizedClass(cls);
        return cls;
    }
    ASSERT(cls == remapClass(cls));

    // fixme verify class is not in an un-dlopened part of the shared cache?

    auto ro = (const class_ro_t *)cls->data();
    auto isMeta = ro->flags & RO_META;
    if (ro->flags & RO_FUTURE) {
        // This was a future class. rw data is already allocated.
        rw = cls->data();
        ro = cls->data()->ro();
        ASSERT(!isMeta);
        cls->changeInfo(RW_REALIZED|RW_REALIZING, RW_FUTURE);
    } else {
        // Normal class. Allocate writeable class data.
        rw = objc::zalloc<class_rw_t>();
        rw->set_ro(ro);
        rw->flags = RW_REALIZED|RW_REALIZING|isMeta;
        cls->setData(rw);
    }

    cls->cache.initializeToEmptyOrPreoptimizedInDisguise();

#if FAST_CACHE_META
    if (isMeta) cls->cache.setBit(FAST_CACHE_META);
#endif

    // Choose an index for this class.
    // Sets cls->instancesRequireRawIsa if indexes no more indexes are available
    cls->chooseClassArrayIndex();

    if (PrintConnecting) {
        _objc_inform("CLASS: realizing class '%s'%s %p %p #%u %s%s",
                     cls->nameForLogging(), isMeta ? " (meta)" : "", 
                     (void*)cls, ro, cls->classArrayIndex(),
                     cls->isSwiftStable() ? "(swift)" : "",
                     cls->isSwiftLegacy() ? "(pre-stable swift)" : "");
    }

    // Realize superclass and metaclass, if they aren't already.
    // This needs to be done after RW_REALIZED is set above, for root classes.
    // This needs to be done after class index is chosen, for root metaclasses.
    // This assumes that none of those classes have Swift contents,
    //   or that Swift's initializers have already been called.
    //   fixme that assumption will be wrong if we add support
    //   for ObjC subclasses of Swift classes.
    supercls = realizeClassWithoutSwift(remapClass(cls->getSuperclass()), nil);
    metacls = realizeClassWithoutSwift(remapClass(cls->ISA()), nil);

#if SUPPORT_NONPOINTER_ISA
    if (isMeta) {
        // Metaclasses do not need any features from non pointer ISA
        // This allows for a faspath for classes in objc_retain/objc_release.
        cls->setInstancesRequireRawIsa();
    } else {
        // Disable non-pointer isa for some classes and/or platforms.
        // Set instancesRequireRawIsa.
        bool instancesRequireRawIsa = cls->instancesRequireRawIsa();
        bool rawIsaIsInherited = false;
        static bool hackedDispatch = false;

        if (DisableNonpointerIsa) {
            // Non-pointer isa disabled by environment or app SDK version
            instancesRequireRawIsa = true;
        }
        else if (!hackedDispatch  &&  0 == strcmp(ro->getName(), "OS_object"))
        {
            // hack for libdispatch et al - isa also acts as vtable pointer
            hackedDispatch = true;
            instancesRequireRawIsa = true;
        }
        else if (supercls  &&  supercls->getSuperclass()  &&
                 supercls->instancesRequireRawIsa())
        {
            // This is also propagated by addSubclass()
            // but nonpointer isa setup needs it earlier.
            // Special case: instancesRequireRawIsa does not propagate
            // from root class to root metaclass
            instancesRequireRawIsa = true;
            rawIsaIsInherited = true;
        }

        if (instancesRequireRawIsa) {
            cls->setInstancesRequireRawIsaRecursively(rawIsaIsInherited);
        }
    }
// SUPPORT_NONPOINTER_ISA
#endif

    // Update superclass and metaclass in case of remapping
    cls->setSuperclass(supercls);
    cls->initClassIsa(metacls);

    // Reconcile instance variable offsets / layout.
    // This may reallocate class_ro_t, updating our ro variable.
    if (supercls  &&  !isMeta) reconcileInstanceVariables(cls, supercls, ro);

    // Set fastInstanceSize if it wasn't set already.
    cls->setInstanceSize(ro->instanceSize);

    // Copy some flags from ro to rw
    if (ro->flags & RO_HAS_CXX_STRUCTORS) {
        cls->setHasCxxDtor();
        if (! (ro->flags & RO_HAS_CXX_DTOR_ONLY)) {
            cls->setHasCxxCtor();
        }
    }
    
    // Propagate the associated objects forbidden flag from ro or from
    // the superclass.
    if ((ro->flags & RO_FORBIDS_ASSOCIATED_OBJECTS) ||
        (supercls && supercls->forbidsAssociatedObjects()))
    {
        rw->flags |= RW_FORBIDS_ASSOCIATED_OBJECTS;
    }

    // Connect this class to its superclass's subclass lists
    if (supercls) {
        addSubclass(supercls, cls);
    } else {
        addRootClass(cls);
    }

    // Attach categories
    methodizeClass(cls, previously);

    return cls;
}

终于来到了我们想要了解日常写的代码是如何加载的地方了。

4 总结

在编译过程中,我们把我们上层的代码转换成中间代码,在经过dyld链接动态库,运行所有初始化程序的时候其中初始化objc库,进入objc_init。其中包括objc环境初始化处理,线程key初始化处理,静态构造函数处理,运行时初始化处理,缓存初始化处理,启动回调机制。
类的加载的一种方式是在map_imagesread_images实现realizeClassWithoutSwift,后面将会继续探索realizeClassWithoutSwift中类的具体实现。