前言

一、environ_init 读取影响运行时的环境变量

二、tls_init 、static_init 、runtime_init

三、exception_init 以及崩溃监听

四、cache_t::init() 和 _imp_implementationWithBlock_init

五、map_images和load_images 调用时机

六、map_images分析

七、readClass分析


经过对应用程序加载的大致分析,基本上了解了app启动的全过程。但是类里面的方法、属性、协议、成员变量是怎么映射进来的呢?包括Class中的ro-> rw ->rwe(为了减少内存开销,将运行时才添加的方法或者属性(关联对象)等保存到rwe) 的数据都是怎么拿到的呢?

那么我们就再继续探索类的整个加载流程。
再探索之前,我们先来分析一下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
}

一、environ_init 读取影响运行时的环境变量

/***********************************************************************
* environ_init
* Read environment variables that affect the runtime.
* Also print environment variable help, if requested.
 * environ_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.
        // 当 setuid 或 setgid 时,所有环境变量都会被静默忽略
        // 这包括 OBJC_HELP 和 OBJC_PRINT_OPTIONS 本身。
        return;
    } 
    bool PrintHelp = true;
    bool PrintOptions = true;
    bool maybeMallocDebugging = false;
    ......
    // 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.
    ......

    // Print OBJC_HELP and OBJC_PRINT_OPTIONS output.
    if (PrintHelp  ||  PrintOptions) {
       .....
        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);
        }
    }
}

我们将PrintHelp和PrintOptions设置为true,然后运行,可以看到打印出了很多的环境变量。
简单的说几个环境变量:
1、OBJC_DISABLE_NONPOINTER_ISA 是否关掉非指针类型的isa,也就是设置isa只保存class的地址。
2、OBJC_PRINT_LOAD_METHODS 打印所有实现+(void)load函数的类。
或者在终端输入:export OBJC_HELP=1 命令。

二、tls_init 、static_init 、runtime_init

1、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
}

pthread_key_create第一个参数为指向一个键值的指针,第二个参数指明了一个destructor函数,如果这个参数不为空,那么当每个线程结束时,系统将调用这个函数来释放绑定在这个键上的内存块。

2、static_init

运行当前objc库中的C ++静态构造函数。在dyld调用我们的静态构造函数之前,libc 会调用 _objc_init(),
因此我们必须自己做

3、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().
 * 初始化 libobjc 的异常处理系统。
 * 由 map_images() 调用。
**********************************************************************/
void exception_init(void)
{
    old_terminate = std::set_terminate(&_objc_terminate);
}

注意crash并不是崩溃,而是系统发出的一些不允许的规定。
跟着_objc_terminate这个回调函数继续看源码:

/***********************************************************************
* _objc_terminate
* Custom std::terminate handler.
*
* The uncaught exception callback is implemented as a std::terminate handler. 
* 1. Check if there's an active exception
* 2. If so, check if it's an Objective-C exception
* 3. If so, call our registered callback with the object.
* 4. Finally, call the previous terminate handler.
 * 1. 检查是否有活动异常
 * 2. 如果是,检查它是否是 Objective-C 异常
 * 3. 如果是,则使用该对象调用我们注册的回调。
 * 4. 最后,调用前面的终止处理程序。
**********************************************************************/
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.
        @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)();
        }
    }
}

可以看到在catch里面有一个uncaught_handler

/***********************************************************************
* _objc_default_uncaught_exception_handler
* Default uncaught exception handler. Expected to be overridden by Foundation.
 * _objc_default_uncaught_exception_handler
 * 默认的未捕获异常处理程序。 预计会被基金会覆盖。
**********************************************************************/
static void _objc_default_uncaught_exception_handler(id exception)
{
}
static objc_uncaught_exception_handler uncaught_handler = _objc_default_uncaught_exception_handler;

uncaught_handler默认为_objc_default_uncaught_exception_handler,我们可以在上层这设置这个回调函数,从而达到检测系统崩溃的目的。

/***********************************************************************
* objc_setUncaughtExceptionHandler
* Set a handler for uncaught Objective-C exceptions. 
* Returns the previous handler.
 * objc_setUncaughtExceptionHandler
 * 为未捕获的 Objective-C 异常设置处理程序。
 * 返回前一个处理程序。
**********************************************************************/
objc_uncaught_exception_handler 
objc_setUncaughtExceptionHandler(objc_uncaught_exception_handler fn)
{
    objc_uncaught_exception_handler result = uncaught_handler;
    uncaught_handler = fn;
    return result;
}

通过objc_setUncaughtExceptionHandler可以设置。

简单的来说就是:由于上层代码的问题(比如数据越界,野指针等),系统发出了不允许的指令,紧接着会抛出异常,如果上层设置了异常回调函数,在抛出异常的时候,会执行这个回调函数,最后调用终止程序。

知道了底层的原理之后,我们在上层做一个监听:

#import "MyUncaughExceptionHandle.h"

@implementation MyUncaughExceptionHandle

void MyExceptionHandlers(NSException *exception) {
    NSLog(@"%s",__func__);
    //在这里获取堆栈信息等
}

+ (void)inistallUncaughtSinalExceptionHandler {
    //NSSetUncaughtExceptionHandler是objc_setUncaughtExceptionHandler的上层封装
    NSSetUncaughtExceptionHandler(&MyExceptionHandlers);
}

关于崩溃的监听流程以及上报服务器,后面我会自己具体的去做一个工具。

四、cache_t::init() 和 _imp_implementationWithBlock_init

cache_t::init() 就是缓存条件的初始化
_imp_implementationWithBlock_init 就是启动回调机制。通常这不会做什么,因为所有的初始化都
是惰性的,但是对于某些进程,我们会迫不及待地加载trampolines dylib

五、map_images和load_images 调用时机

先回顾,在dyld加载的过程中,为什么会来到_objc_init。因为_objc_init是runtime,运行时的加载,肯定会加载类的信息。
核心代码:

_dyld_objc_notify_register(&map_images, load_images, unmap_image);

_objc_init中确实向dyld注册了map_images和load_images,那么map_images和load_images它们是什么时候被调用的呢?
我们都知道,我们写的代码会通过编译->汇编>链接,最终生成可执行文件。但是当我运行一个app的时候,首先应该把可执行文件的信息加载映射到内存里面,我们才可以去使用。

map_images 是管理文件中和动态库中所有的符号,而load_images之前分析过是加载执行load方法。

那么回到最开始我提的问题,类的信息是如何被加载的呢?这个就和map_images有关系了。

我们先来分析一下map_images和load_images是什么时候被调用的。

_dyld_objc_notify_register的注册函数中,map_images传入的是指针,load_images传入的是函数。

例如

void test() {
}
int main(int argc, char *argv[])
{
    printf("0x%x\n",test);
    printf("0x%x\n",&test);
}
//因此,对于test和&test你应该这样理解,test是函数的首地址,它的类型是void (),&test表示一个指向函数test这个对象的地址, 
//它的类型是void (*)(),因此test和&test所代表的地址值是一样的,但类型不一样。test是一个函数,&test表达式的值是一个指针! 

因为map_images 会在下层改变的时候改变,load_images只是去调用load方法,所有一个是指针(引用类型),一个是函数(值类型)。
例子:

@implementation ViewController

//定义函数指针
//函数返回值类型 (* 指针变量名) (函数参数列表);
typedef void (*_dyld_test_objc_notify_init)();
//生命函数指针变量
static _dyld_test_objc_notify_init test_init;

void test() {
    printf("输出");
}

void registerMethod(_dyld_test_objc_notify_init init) {
    test_init = init;
    //函数名就是地址,而函数名对应的地址里存放的内容仍然是函数的地址,所以无论取几次星号,结果都一样。
    (*test_init)();
}

- (void)viewDidLoad {
    [super viewDidLoad];
    registerMethod(test);
}
@end

接下来进入dyld的源码,跨库调用了 _dyld_objc_notify_register

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

接着进入registerObjCNotifiers,发现将objc中的map_images、load_images、unmapped都赋值给了dyld中的sNotifyObjCMapped、sNotifyObjCInit、sNotifyObjCUnmapped

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

那么我们全局搜索一下sNotifyObjCMapped、sNotifyObjCInit、sNotifyObjCUnmapped 就知道他们在哪里调用了。sNotifyObjCInit 之前分析过了,在镜像文件初始化完成之后会去调用,sNotifyObjCUnmapped是应用程序结束的时候,dyld的卸载释放相关,这里不做重点分析。
我们重点来分析一下sNotifyObjCMapped在哪里调用。继续看源码:

static void notifyBatchPartial(dyld_image_states state, bool orLater, dyld_image_state_change_handler onlyHandler, bool preflightOnly, bool onlyObjCMappedNotification) {
    ...
// tell objc about new images
			if ( (onlyHandler == NULL) && ((state == dyld_image_state_bound) || (orLater && (dyld_image_state_bound > state))) && (sNotifyObjCMapped != NULL) ) {
				.....
				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);
				}
			}
    ...
}

全局搜索一下,看到只有在notifyBatchPartial函数中调用,那么registerObjCNotifiers中就有notifyBatchPartial的调用。当然还有其他地方调用,就不一一去分析了。
所以,在注册回调函数的同时,就会去调用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_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;
    // 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 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;
                }
            }
            
            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.
     // 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.
    // 执行一次性运行时初始化,必须推迟到
    // 找到可执行文件本身。 这需要在
    // 进一步初始化之前完成。
    //(如果可执行文件不包含Objective-C 代码但Objective-C
    // 稍后动态加载,则该信息列表中可能不存在该可执行文件。
    if (firstTime) {
        sel_init(selrefCount);
        arr_init();
    }

    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,读取镜像文件。
继续来看_read_images的核心方法:

void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int unoptimizedTotalClasses)
{
    //1.条件控制进行一次的加载
    .....

#define EACH_HEADER \
    hIndex = 0;         \
    hIndex < hCount && (hi = hList[hIndex]); \
    hIndex++

    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

#endif

        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
        //设置hash表,存放类
        int namedClassesSize =
            (isPreoptimized() ? unoptimizedTotalClasses : totalClasses) * 4 / 3;
        gdb_objc_realized_classes =
            NXCreateMapTable(NXStrValueMapPrototype, namedClassesSize);

        ts.log("IMAGE TIMES: first time tasks");
    }
    //2.修复预编译阶段的'@selector'的混乱问题
    // 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;
                }
            }
        }
    }

    //3.错误混乱的类处理
    // 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;
        }

        //GETSECT(_getObjc2ClassList,           classref_t const,      "__objc_classlist");
        //从macho里面读取来classlist
        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");

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

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

    //5.修复一些消息
#if SUPPORT_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");
#endif

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

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

    //7.修复没有被加载的协议
    // Fix up @protocol references
    // Preoptimized images may have the right
    // answer already but we don't know for sure.
    // 修正@protocol 引用 // 预先优化的图像可能已经有正确的 // 答案,但我们不确定。
    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");

    //8.分类处理
    // 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
    // 发现类别。 仅在初始类别
    // 附件完成后执行此操作。 对于启动时出现的类别,
    // 发现被推迟到
    // _dyld_objc_notify_register 调用完成后的第一个 load_images 调用。  rdar://问题/53119145
    if (didInitialAttachCategories) {
        for (EACH_HEADER) {
            load_categories_nolock(hi);
        }
    }

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

    //9.类的加载处理
    // 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");

    //10.没有被处理的类,优化防止被侵犯
    //实现新解析的未来类,以防 CF 操作它们
    // Realize newly-resolved future classes, in case CF manipulates them
    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");

    if (DebugNonFragileIvars) {
        realizeAllClasses();
    }

    // Print preoptimization statistics
    //打印预优化统计信息
    if (PrintPreopt) {
        static unsigned int PreoptTotalMethodLists;
        static unsigned int PreoptOptimizedMethodLists;
        static unsigned int PreoptTotalClasses;
        static unsigned int PreoptOptimizedClasses;
        .....
    }

#undef EACH_HEADER
}

通过read_image的函数,可以大致的总结做的事情如下:

1、条件控制进行一次的加载

可以看到会进行对小对象的处理initializeTaggedPointerObfuscator。
然后会创建一张hash表,用来存放类的名称等信息。
负载因子 = 填入表中元素的个数 / 散列表的长度

 //负载因子是0.75的时候,空间利用率比较高,而且避免了相当多的Hash冲突
// 4/3 is NXMapTable's load factor
        int namedClassesSize = 
            (isPreoptimized() ? unoptimizedTotalClasses : totalClasses) * 4 / 3;
        gdb_objc_realized_classes =
            NXCreateMapTable(NXStrValueMapPrototype, namedClassesSize);

2、修复预编译阶段的'@selector'的混乱问题

由于方法函数都是从macho中读取的,每一个库中的sel虽然名字相同,但是地址是不同的,最终要全部加载到内容中时需要修复一致性的问题。

比如retain方法,sel的名字是一样的,但是地址不一样,也就证明了,sel是带地址的字符串。

3、错误混乱的类处理

处理类的信息发生的混乱的情况,但是很少会发生。这里注意,如果类发生混乱,是直接被删除然后重新创建,但不是被移动,因为如果移动的话,性能损耗要大。
在这步处理的过程中,有一个readClass

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

我们打印一下cls和newCls

可以看到,经过readClass之后,cls的地址就和类进行了绑定,所以readClass后面我们具体分析。

4、修复重映射一些没有被镜像文件加载进来的类

一般很少会发生,暂时不进行研究。

5、修复一些消息

一般也很少发生,暂时不进行研究。

6、当我们类里面有协议的时候:readProtocol

对协议的处理,后面再具体分析

7、修复没有被加载的协议

搭配协议的处理,后面再具体分析

8、分类处理(已经被推迟到load_image中)

可以看到代码的注释,分类处理被推迟到_dyld_objc_notify_register 调用完成后的第一个 load_images 调用。

9、类的加载处理

只处理非懒加载类。后面会具体的分析什么是懒加载和非懒加载类。

10、没有被处理的类,优化防止被侵犯

一般也很少发生,暂时不进行研究。

七、readClass分析

/***********************************************************************
* readClass
* Read a class and metaclass as written by a compiler.
* Returns the new class pointer. This could be: 
* - cls
* - nil  (cls has a missing weak-linked superclass)
* - something else (space for this class was reserved by a future class)
*
* Note that all work performed by this function is preflighted by 
* mustReadClasses(). Do not change this function without updating that one.
*
* Locking: runtimeLock acquired by map_images or objc_readClassPair
 * readClass * 读取由编译器编写的类和元类。
 * 返回新的类指针。 这可能是: * - cls
 * - nil(cls 缺少弱链接超类)
 * - 其他(此类的空间由未来的类保留)
 * * 请注意,此函数执行的所有工作都由 * 预检 mustReadClasses()。 不要在不更新该功能的情况下更改此功能。
 * * 锁定:map_images 或 objc_readClassPair 获取的 runtimeLock
**********************************************************************/
Class readClass(Class cls, bool headerIsBundle, bool headerIsPreoptimized)
{
    const char *mangledName = cls->nonlazyMangledName();
    const char *myclassNme = "MyTestObjc";
    if (strcmp(mangledName, myclassNme) == 0) {
        printf("===");
    }
    if (missingWeakSuperclass(cls)) {
     .............
    }
    
    cls->fixupBackwardDeployingStableSwift();

    Class replacing = nil;
    if (mangledName != nullptr) {
        //可能被未来处理的
    ..........................
    }
    
    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.");
        }
        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;
}

首先读取类的名称,然后核心方法执行addNamedClass:

/***********************************************************************
* addNamedClass
* Adds name => cls to the named non-meta class map.
* Warns about duplicate class names and keeps the old mapping.
* Locking: runtimeLock must be held by the caller
 * 将 name => cls 添加到命名的非元类映射。
 * 警告重复的类名并保留旧的映射。
 * 锁定:runtimeLock 必须由调用者持有
**********************************************************************/
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));
    // wrong: constructed classes are already realized when they get here
    // ASSERT(!cls->isRealized());
}

将类的地址,名称等信息插入数据表,并绑定类信息(添加到命名的非元类映射)。
然后通过addClassTableEntry,添加类到所有类的表中

/***********************************************************************
* addClassTableEntry
* Add a class to the table of all classes. If addMeta is true,
* automatically adds the metaclass of the class as well.
* Locking: runtimeLock must be held by the caller.
 * 将一个类添加到所有类的表中。 如果 addMeta 为 true,
 * 也会自动添加类的元类。  * 锁定:runtimeLock 必须由调用者持有。
**********************************************************************/
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);
}

而这张类的表是在runtime_init的时候创建的:

void runtime_init(void)
{
    //分类处理的初始化
    objc::unattachedCategories.init(32);
    //初始化存放类的表(储存加载完毕的类)
    objc::allocatedClasses.init();
}

整个read_class的流程就是从macho中将类的信息(__objc_classlist)读到内存(ClassTable)中来。

补充:在整个read_images->read_class的过程中,出现了两张表,一个是gdb_objc_realized_classes,另一个是allocatedClasses。

1、gdb_objc_realized_classes是对所有的类进行缓存:从对应的 section 中读取所有的类,取出来后以 mangledName 作为键,以 objc_class结构体的指针作为值。是在_read_images中创建的,是类的总表。

2、allocatedClasses是用来存放已经开辟了内存的类表。是在runtime_init中就创建了。

gdb_objc_realized_classes对allocatedClasses是一种包含的关系。