iOS-类的加载(一)

1,503 阅读15分钟

问题先行

1、Xcode项目的环境变量在哪里设置?常用的有哪些?
2、对于NSException应用级异常如何收集?
3、什么是懒加载类和非懒加载类,为什么?
4、懒加载类和非懒加载类的初始化时机分别是?

资源准备

1、objc源码下载opensource.apple.com/

_objc_init

通过分析应用程序的加载,知道dyld链接器与objc库关联的函数为_objc_init。其也是objc类加载的入口方法。

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

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

environ_init()方法是初始化一系列环境变量,并读取影响运行时的环境变量。
当然环境变量的配置如下target->Edit Scheme->Run->Arguments->Environment Variables 通过配置相关环境变量在控制台打印日志信息,例如:打印项目中ClassCategory+ (void)load 方法的调用信息

image.png

image.png

tls_init

tls_init()方法是关于线程key的绑定,主要是本地线程池初始化以及析构

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

static_init

通过注释即可知道该方法会运行C++静态构造函数,在dyld调用静态构造函数之前,libc会调用_objc_init(),所以必须自己去实现静态函数的调用。

/***********************************************************************
* static_init
* Run C++ static constructor functions.
* libc calls _objc_init() before dyld would call our static constructors, 
* so we have to do it ourselves.
**********************************************************************/
static void static_init()
{
    size_t count;
    auto inits = getLibobjcInitializers(&_mh_dylib_header, &count);
    for (size_t i = 0; i < count; i++) {
        inits[i]();
    }
    auto offsets = getLibobjcInitializerOffsets(&_mh_dylib_header, &count);
    for (size_t i = 0; i < count; i++) {
        UnsignedInitializer init(offsets[i]);
        init();
    }
}

runtime_init

主要是运行时的初始化,主要分为两部分:分类初始化类的表初始化

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

exception_init

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

/***********************************************************************
* exception_init
* Initialize libobjc's exception handling system.
* Called by map_images().
**********************************************************************/
void exception_init(void)
{
    old_terminate = std::set_terminate(&_objc_terminate);
}

/***********************************************************************
* _objc_terminate
* 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.
**********************************************************************/
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)();
        }
    }
}

1、通过上面源码的注释可以看出,当有exception发生时,会来到_objc_terminate方法,走到uncaught_handler扔出异常
2、uncaught_handler可以在app层会传入一个函数用于处理异常,以便于调用函数,回到原有的app层中。
3、如下所示,其中fn即为传入的函数,即 uncaught_handler 等于 fn

/***********************************************************************
* _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_setUncaughtExceptionHandler
* Set a handler for uncaught Objective-C exceptions. 
* Returns the previous handler. 
**********************************************************************/
objc_uncaught_exception_handler 
objc_setUncaughtExceptionHandler(objc_uncaught_exception_handler fn)
{
    objc_uncaught_exception_handler result = uncaught_handler;
    uncaught_handler = fn;
    return result;
}

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

该方法主要是启动回调机制,可通过注释了解详情。

/// Initialize the trampoline machinery. Normally this does nothing, as
/// everything is initialized lazily, but for certain processes we eagerly load
/// the trampolines dylib.
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

这个方法的作用可以在iOS-应用的加载中查看,总结如下

  • 参数map_imagesdyldimage(镜像文件)加载进内存时,会触发该函数
  • 参数load_imagedyld初始化image会触发该函数
  • 参数unmap_imagedyldimage移除时,会触发该函数
  • 仅供objc运行时使用
  • 注册处理程序,以便在映射、取消映射和初始化objc图像时调用
  • dyld将会通过一个包含objc-image-info的镜像文件的数组回调mapped函数

dyld与objc的关联

关系

objc中源码如下

_dyld_objc_notify_register(&map_images, load_images, unmap_image);

dyld中源码如下

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

通过上面可以看出如下关系
map_images==>mapped==>sNotifyObjCMapped
load_images==>init==>sNotifyObjCInit unmap_image==>unmapped==>sNotifyObjCUnmapped

调用时机

map_images调用路径如下\

_dyld_objc_notify_register(objc) -> registerObjCNotifiers(dyld) -> notifyBatchPartial(dyld) -> sNotifyObjCMapped(dyld)

void registerObjCNotifiers(_dyld_objc_notify_mapped mapped, _dyld_objc_notify_init init, _dyld_objc_notify_unmapped unmapped)
{
	/// 代码省略
	try {
		notifyBatchPartial(dyld_image_state_bound, true, NULL, false, true);
	}
	catch (const char* msg) {
		// ignore request to abort during registration
	}
	/// 代码省略
}
static void notifyBatchPartial(dyld_image_states state, bool orLater, dyld_image_state_change_handler onlyHandler, bool preflightOnly, bool onlyObjCMappedNotification)
{
    /// 代码省略
	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);
	}
    /// 代码省略
}

load_images调用路径如下

recursiveInitialization(dyld) -> notifySingle(dyld) -> sNotifyObjCInit(objc)

static void notifySingle(dyld_image_states state, const ImageLoader* image, ImageLoader::InitializerTimingList* timingInfo)
{
	///代码省略
	if ( (state == dyld_image_state_dependents_initialized) && (sNotifyObjCInit != NULL) && image->notifyObjC() ) {
		uint64_t t0 = mach_absolute_time();
		dyld3::ScopedTimer timer(DBG_DYLD_TIMING_OBJC_INIT, (uint64_t)image->machHeader(), 0, 0);
		(*sNotifyObjCInit)(image->getRealPath(), image->machHeader());
		uint64_t t1 = mach_absolute_time();
		uint64_t t2 = mach_absolute_time();
		uint64_t timeInObjC = t1-t0;
		uint64_t emptyTime = (t2-t1)*100;
		if ( (timeInObjC > emptyTime) && (timingInfo != NULL) ) {
			timingInfo->addTime(image->getShortName(), timeInObjC);
		}
	}
	///代码省略
}

总结

  • registerObjCNotifiers方法中,我们把_dyld_objc_notify_register传入的map_images赋值给sNotifyObjCMapped,将load_images赋值给sNotifyObjCInit,将unmap_image赋值给sNotifyObjCUnmapped
  • registerObjCNotifiers方法中,我们将传参赋值后就开始调用notifyBatchPartial()方法。
  • notifyBatchPartial()方法中会调用(*sNotifyObjCMapped)(objcImageCount, paths, mhs)即而触发map_images方法。
  • dyldrecursiveInitialization方法在调用完bool hasInitializers = this->doInitialization(context)方法后,会调用notifySingle()方法。
  • notifySingle()中会调用(*sNotifyObjCInit)(image->getRealPath(), image->machHeader())即而触发load_images方法。
  • sNotifyObjCUnmapped会在removeImage方法里触发,字面理解就是删除Image(映射的镜像文件) 所以有以下结论:map_images是先于load_images调用,即先map_images,再load_images

map_images

当镜像文件加载到内存时map_images会触发,即map_images方法的主要作用是将Mach-O中的类信息加载到内存。

/***********************************************************************
* map_images
* Process the given images which are being mapped in by dyld.
* Calls ABI-agnostic code after taking ABI-specific locks.
*
* Locking: write-locks runtimeLock
**********************************************************************/
void
map_images(unsigned count, const char * const paths[],
           const struct mach_header * const mhdrs[])
{
    mutex_locker_t lock(runtimeLock);
    return map_images_nolock(count, paths, mhdrs);
}
void 
map_images_nolock(unsigned mhCount, const char * const mhPaths[],
                  const struct mach_header * const mhdrs[])
{
    /// 代码省略......
    if (hCount > 0) {
    /// 主要方法
        _read_images(hList, hCount, totalClasses, unoptimizedTotalClasses);
    }
    /// 代码省略......
}

发现_read_images为最后的核心方法,大致功能如下

1、条件控制进行一次的加载
2、修复预编译阶段@selector混乱问题
3、错误混乱的类处理
4、修复重映射没有被镜像文件加载进来的类
5、修复一些消息
6、当我们的类里面有协议的时候:readProtocol读取协议
7、修复没有被加载的协议
8、分类的处理
9、类的加载处理
10、没有被处理的类,优化那些被侵犯的类

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

doneOnce流程中通过NXCreateMapTable创建表,存放类信息,即创建一张类的哈希表。这个哈希表用于存储不在共享缓存且已命名类,无论类是否实现其容量是类数量的4/3。关键代码如下:

/// 1️⃣条件控制进行的一次加载
if (!doneOnce) {
    doneOnce = YES;
    launchTime = YES;

    /// 代码省略。。。

    /// TaggedPointers的优化处理
    if (DisableTaggedPointers) {
        disableTaggedPointers();
    }

    /// 代码省略。。。

    // namedClasses
    // Preoptimized classes don't go in this table.
    // 4/3 is NXMapTable's load factor
    int namedClassesSize = 
        (isPreoptimized() ? unoptimizedTotalClasses : totalClasses) * 4 / 3;
    /// 创建表(哈希表 key-value)
    gdb_objc_realized_classes =
        NXCreateMapTable(NXStrValueMapPrototype, namedClassesSize);

    ts.log("IMAGE TIMES: first time tasks");
}

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

获取Mach_O中的静态段__objc_selrefs,遍历列表将sel插入namedSelectors哈希表,如地址不一致,调整为一致。

/// 2️⃣修复预编译阶段的@selector的混乱问题
static size_t UnfixedSelectors;
{
    mutex_locker_t lock(selLock);
    for (EACH_HEADER) {
        if (hi->hasPreoptimizedSelectors()) continue;

        bool isBundle = hi->isBundle();
        /// 拿到Mach_O中的静态段__objc_selrefs
        SEL *sels = _getObjc2SelectorRefs(hi, &count);
        UnfixedSelectors += count;
        for (i = 0; i < count; i++) {
            const char *name = sel_cname(sels[i]);
            /// 将sel插入namedSelectors哈希表
            SEL sel = sel_registerNameNoLock(name, isBundle);
            /// 当地址不一致时,调整为一致的
            if (sels[i] != sel) {
                sels[i] = sel;
            }
        }
    }
}

3、错误混乱的类处理

主要是从Mach-O中取出所有类,在遍历进行处理。

/// 3️⃣错误混乱的类处理
bool hasDyldRoots = dyld_shared_cache_some_image_overridden();

for (EACH_HEADER) {
    if (! mustReadClasses(hi, hasDyldRoots)) {
        // Image is sufficiently optimized that we need not call readClass()
        continue;
    }
    /// 从编译后的类列表中取出所有类,即从Mach-O中获取静态段__objc_classlist
    classref_t const *classlist = _getObjc2ClassList(hi, &count);

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

    for (i = 0; i < count; i++) {
        /// 此时的cls只是一个地址
        Class cls = (Class)classlist[i];
        /// 读取类,经过这步后,cls获取的值才是一个名字
        Class newCls = readClass(cls, headerIsBundle, headerIsPreoptimized);

        if (newCls != cls  &&  newCls) {
            // Class was moved but not deleted. Currently this occurs 
            // only when the new class resolved a future class.
            // Non-lazily realize the class below.
            resolvedFutureClasses = (Class *)
                realloc(resolvedFutureClasses, 
                        (resolvedFutureClassCount+1) * sizeof(Class));
            resolvedFutureClasses[resolvedFutureClassCount++] = newCls;
        }
    }
}

readClass为核心方法,主要作用就是将Mach-O中的类读取到内存即插入表中,但是目前的类仅有两个信息:地址以及名称,而mach-O的其中的data数据还未读取出来。

Class readClass(Class cls, bool headerIsBundle, bool headerIsPreoptimized)
{
    /// 获取类的名字
    const char *mangledName = cls->nonlazyMangledName();
    /// 当前类的父类中若有丢失的weak-linked类,则返回nil
    if (missingWeakSuperclass(cls)) {
        // No superclass (probably weak-linked). 
        // Disavow any knowledge of this subclass.
        if (PrintConnecting) {
            _objc_inform("CLASS: IGNORING class '%s' with "
                         "missing weak-linked superclass", 
                         cls->nameForLogging());
        }
        addRemappedClass(cls, nil);
        cls->setSuperclass(nil);
        return nil;
    }
    
    cls->fixupBackwardDeployingStableSwift();

    Class replacing = nil;
    if (mangledName != nullptr) {
        /// 正常情况下不会走进popFutureNamedClass判断,这是专门针对未来的待处理的类的特殊操作,
        /// 因此也不会对ro、rw进行操作(创建类和系统类都不会进入)
        if (Class newCls = popFutureNamedClass(mangledName)) {
            代码省略......
        }
    }
    /// 判断类是否已经加载加载到内存
    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
            /// 将当前类添加到已经创建好的gdb_objc_realized_classes哈希表,该表用于存放所有类
            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.");
        }
        /// 插入表,即相当于从Mach-O文件读取到内存中
        /// 将初始化的类添加到allocatedClasses表,这个表在_objc_init中的runtime_init就初始化创建了
        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;
}

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

主要是将未映射的Class 和Super Class进行重映射

/// 4️⃣修复重映射一些没有被镜像文件加载进来的类
if (!noClassesRemapped()) {
    for (EACH_HEADER) {
        /// 获取Mach-O中的静态段__objc_classrefs
        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?
        /// 获取Mach-O中的静态段__objc_superrefs
        classrefs = _getObjc2SuperRefs(hi, &count);
        for (i = 0; i < count; i++) {
            remapClassRef(&classrefs[i]);
        }
    }
}

5、修复一些消息

主要是获取Mach-O的静态段__objc_msgrefs,并遍历通过fixupMessageRef将函数指针进行注册,并fix为新的函数指针

/// 5️⃣修复一些消息
for (EACH_HEADER) {
    /// 获取Mach-O的静态段__objc_msgrefs
    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());
    }
    /// 遍历将函数指针进行注册,并fix为新的函数指针
    for (i = 0; i < count; i++) {
        fixupMessageRef(refs+i);
    }
}

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

创建protocol(protocol_map)哈希表,获取到Mach-O中的静态段__objc_protolist协议列表。循环遍历协议列表,通过readProtocol方法将协议添加到protocol_map哈希表中。

/// 6️⃣当我们的类里面有协议的时候:readProtocol读取协议
for (EACH_HEADER) {
    extern objc_class OBJC_CLASS_$_Protocol;
    Class cls = (Class)&OBJC_CLASS_$_Protocol;
    ASSERT(cls);
    /// 获取protocols哈希表(protocol_map)
    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();
    /// 获取Mach-O中的静态段__objc_protolist协议列表
    protocol_t * const *protolist = _getObjc2ProtocolList(hi, &count);
    for (i = 0; i < count; i++) {
        /// 添加protocol到protocol_map哈希表中
        readProtocol(protolist[i], cls, protocol_map, 
                     isPreoptimized, isBundle);
    }
}

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

获取到Mach-O的静态段__objc_protorefs,比较当前协议和协议列表中的同一个内存地址的协议是否相同,如果不同则替换。

/// 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;
    /// 获取到Mach-O的静态段 __objc_protorefs
    protocol_t **protolist = _getObjc2ProtocolRefs(hi, &count);
    for (i = 0; i < count; i++) {
        /// 比较当前协议和协议列表中的同一个内存地址的协议是否相同,如果不同则替换
        remapProtocolRef(&protolist[i]);
    }
}

8、分类的处理

通过注释可知,主要是处理分类,需要在分类初始化并将数据加载到类后才执行,对于运行时出现的分类,将分类的发现推迟到对_dyld_objc_notify_register的调用完成后的第一个load_images调用为止

// 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
/// 8️⃣分类的处理
if (didInitialAttachCategories) {
    for (EACH_HEADER) {
        load_categories_nolock(hi);
    }
}

9、类的加载处理

先了解下懒加载类和非懒加载类的知识点

  • 懒加载类:类没有实现load方法,在使用的第一次才会加载,当我们在给这个类发送消息,如果是第一次,在消息查找的过程中就会判断这个类是否加载,没有加载就会加载这个类
  • 非懒加载类:类的内部实现了load方法,类的加载就会提前。

苹果官方定义
NonlazyClass is all about a class implementing or not a +load method.

获取Mach-O的静态段__objc_nlclslist非懒加载类表,将非懒加载类插入类表,存储到内存,如果已经添加就不会载添加,需要确保整个结构都被添加。通过realizeClassWithoutSwift(核心方法)实现当前的类。

/// 9️⃣类的加载处理
for (EACH_HEADER) {
    /// 获取Mach-O的静态段__objc_nlclslist非懒加载类表
    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);
    }
}

realizeClassWithoutSwift方法主要功能,读取classdata数据,并设置rorw,递归调用realizeClassWithoutSwift完善继承链,通过methodizeClass(核心方法之一)方法化类。

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

    class_rw_t *rw;
    Class supercls;
    Class metacls;
    /// isa找到根元类之后,根元类的isa是指向自己,并不会返回nil,
    /// 所以有以下递归终止条件,其目的是保证类只加载一次
    /// 如果类不存在,则返回nil,如果类已经实现,则直接返回cls
    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?
    /// 读取class的data数据,并设置ro、rw
    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.
        /// 申请开辟空间zalloc
        rw = objc::zalloc<class_rw_t>();
        /// rw中的ro设置为临时变量ro
        rw->set_ro(ro);
        rw->flags = RW_REALIZED|RW_REALIZING|isMeta;
        /// 将class的data赋值为rw形式
        cls->setData(rw);
    }

    /// 代码省略......

    /// 递归调用realizeClassWithoutSwift完善继承链,并设置当前类、父类、元类的rw
    supercls = realizeClassWithoutSwift(remapClass(cls->getSuperclass()), nil);
    metacls = realizeClassWithoutSwift(remapClass(cls->ISA()), nil);

    /// 代码省略......

    // Update superclass and metaclass in case of remapping
    /// 将父类和元类给我们的类,分别是isa和父类对应的值
    cls->setSuperclass(supercls);
    cls->initClassIsa(metacls);

    /// 代码省略......

    // Connect this class to its superclass's subclass lists
    /// 双向列表指向关系,父类中可以找到子类,子类中也可以找到父类
    if (supercls) {
        addSubclass(supercls, cls);
    } else {
        addRootClass(cls);
    }

    // Attach categories
    /// 通过methodizeClass方法,
    /// 从ro中读取方法列表(包括分类中的方法)、属性列表、协议列表赋值给rw,并返回cls
    methodizeClass(cls, previously);

    return cls;
}
  • 关于rorw更多信息
  • 通过addSubclass 和 addRootClass设置父子的双向链表指向关系,即父类中可以找到子类,子类中可以找到父类
  • 通过methodizeClass方法,从ro中读取方法列表(包括分类中的方法)、属性列表、协议列表赋值给rw
static void methodizeClass(Class cls, Class previously)
{
    runtimeLock.assertLocked();

    bool isMeta = cls->isMetaClass();
    /// 初始化一个rw
    auto rw = cls->data();
    auto ro = rw->ro();
    auto rwe = rw->ext();

    // Methodizing for the first time
    if (PrintConnecting) {
        _objc_inform("CLASS: methodizing class '%s' %s", 
                     cls->nameForLogging(), isMeta ? "(meta)" : "");
    }

    // Install methods and properties that the class implements itself.
    /// 将属性列表、方法列表、协议列表等加入rw中
    /// 将ro中的方法列表加入到rw中
    method_list_t *list = ro->baseMethods();
    if (list) {
        prepareMethodLists(cls, &list, 1, YES, isBundleClass(cls), nullptr);
        if (rwe) rwe->methods.attachLists(&list, 1);
    }
    /// 加入属性
    property_list_t *proplist = ro->baseProperties;
    if (rwe && proplist) {
        rwe->properties.attachLists(&proplist, 1);
    }
    /// 加入协议
    protocol_list_t *protolist = ro->baseProtocols;
    if (rwe && protolist) {
        rwe->protocols.attachLists(&protolist, 1);
    }

    // Root classes get bonus method implementations if they don't have 
    // them already. These apply before category replacements.
    if (cls->isRootMetaclass()) {
        // root metaclass
        addMethod(cls, @selector(initialize), (IMP)&objc_noop_imp, "", NO);
    }

    // Attach categories.
    /// 加入分类中的方法
    if (previously) {
        if (isMeta) {
            objc::unattachedCategories.attachToClass(cls, previously,
                                                     ATTACH_METACLASS);
        } else {
            // When a class relocates, categories with class methods
            // may be registered on the class itself rather than on
            // the metaclass. Tell attachToClass to look for those.
            objc::unattachedCategories.attachToClass(cls, previously,
                                                     ATTACH_CLASS_AND_METACLASS);
        }
    }
    objc::unattachedCategories.attachToClass(cls, cls,
                                             isMeta ? ATTACH_METACLASS : ATTACH_CLASS);

    /// 代码省略......
}

其主要分为几部分:
1、将属性列表、方法列表、协议列表等加入到rwe
2、方法排序prepareMethodLists
3、加入分类中的方法

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

补充-方法排序

消息转发有讲到方法的查找算法是通过二分查找算法,说明sel-imp是有排序的。核心方法路径methodizeClass->prepareMethodLists->fixupMethodList通过源码可以看出是通过sel地址排序的。

static void 
fixupMethodList(method_list_t *mlist, bool bundleCopy, bool sort)
{
    runtimeLock.assertLocked();
    ASSERT(!mlist->isFixedUp());

    // fixme lock less in attachMethodLists ?
    // dyld3 may have already uniqued, but not sorted, the list
    if (!mlist->isUniqued()) {
        mutex_locker_t lock(selLock);
    
        // Unique selectors in list.
        for (auto& meth : *mlist) {
            const char *name = sel_cname(meth.name());
            meth.setName(sel_registerNameNoLock(name, bundleCopy));
        }
    }

    // Sort by selector address.
    // Don't try to sort small lists, as they're immutable.
    // Don't try to sort big lists of nonstandard size, as stable_sort
    // won't copy the entries properly.
    /// 根据sel地址排序
    if (sort && !mlist->isSmallList() && mlist->entsize() == method_t::bigSize) {
        method_t::SortBySELAddress sorter;
        std::stable_sort(&mlist->begin()->big(), &mlist->end()->big(), sorter);
    }
    
    // Mark method list as uniqued and sorted.
    // Can't mark small lists, since they're immutable.
    if (!mlist->isSmallList()) {
        mlist->setFixedUp();
    }
}

补充-懒加载和非懒加载类初始化时机

  • 【懒加载类】realizeClassWithoutSwift之前这个方法在消息转发的慢速查找中有涉及,也就是说懒加载类在第一次处理消息的时候才去现实类的加载。
  • 【非懒加载类】见如上类的加载流程 具体如下

image.png

load_images

load_images方法的主要作用是加载镜像文件,其中最重要的有两个方法:prepare_load_methods(加载)和call_load_methods(调用)

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

prepare_load_methods

  • 获取所有的非懒加载类,遍历这些类并将load方法添加到loadable_classes数组中保存。
  • 获取所有的非懒加载分类,遍历这些分类并将load方法添加到loadable_categorys数组中保存。
void prepare_load_methods(const headerType *mhdr)
{
    size_t count, i;

    runtimeLock.assertLocked();
    /// 获取所有的非懒加载类
    classref_t const *classlist = 
        _getObjc2NonlazyClassList(mhdr, &count);
    /// 遍历这些类,并将load方法添加到loadable_classes数组中保存,优先添加其父类的load方法
    for (i = 0; i < count; i++) {
        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方法添加到loadable_categorys数组中保存
        add_category_to_loadable_list(cat);
    }
}

schedule_class_load

根据类的继承链递归调用获取load,直到cls不存在才结束递归,目的是为了确保父类的load优先加载

static void schedule_class_load(Class cls)
{
    if (!cls) return;
    ASSERT(cls->isRealized());  // _read_images should realize

    if (cls->data()->flags & RW_LOADED) return;

    // Ensure superclass-first ordering
    schedule_class_load(cls->getSuperclass());
    /// 将load方法和cls类名一起加到loadable_classes表中
    add_class_to_loadable_list(cls);
    cls->setInfo(RW_LOADED); 
}

add_class_to_loadable_list

此方法主要是将load方法和cls类名一起加到loadable_classes表中

void add_class_to_loadable_list(Class cls)
{
    IMP method;

    loadMethodLock.assertLocked();
    /// 获load的方法
    method = cls->getLoadMethod();
    if (!method) return;  // Don't bother if cls has no +load method
    
    if (PrintLoading) {
        _objc_inform("LOAD: class '%s' scheduled for +load", 
                     cls->nameForLogging());
    }
    
    if (loadable_classes_used == loadable_classes_allocated) {
        loadable_classes_allocated = loadable_classes_allocated*2 + 16;
        loadable_classes = (struct loadable_class *)
            realloc(loadable_classes,
                              loadable_classes_allocated *
                              sizeof(struct loadable_class));
    }
    
    loadable_classes[loadable_classes_used].cls = cls;
    loadable_classes[loadable_classes_used].method = method;
    loadable_classes_used++;
}

getLoadMethod

IMP 
objc_class::getLoadMethod()
{
    runtimeLock.assertLocked();

    const method_list_t *mlist;

    ASSERT(isRealized());
    ASSERT(ISA()->isRealized());
    ASSERT(!isMetaClass());
    ASSERT(ISA()->isMetaClass());

    mlist = ISA()->data()->ro()->baseMethods();
    if (mlist) {
        for (const auto& meth : *mlist) {
            const char *name = sel_cname(meth.name());
            if (0 == strcmp(name, "load")) {
                return meth.imp(false);
            }
        }
    }

    return nil;
}

add_class_to_loadable_list

获取所有的非懒加载分类中的load方法,将分类名+load方法加入表loadable_categories

void add_class_to_loadable_list(Class cls)
{
    IMP method;

    loadMethodLock.assertLocked();

    method = cls->getLoadMethod();
    if (!method) return;  // Don't bother if cls has no +load method
    
    if (PrintLoading) {
        _objc_inform("LOAD: class '%s' scheduled for +load", 
                     cls->nameForLogging());
    }
    
    if (loadable_classes_used == loadable_classes_allocated) {
        loadable_classes_allocated = loadable_classes_allocated*2 + 16;
        loadable_classes = (struct loadable_class *)
            realloc(loadable_classes,
                              loadable_classes_allocated *
                              sizeof(struct loadable_class));
    }
    
    loadable_classes[loadable_classes_used].cls = cls;
    loadable_classes[loadable_classes_used].method = method;
    loadable_classes_used++;
}

call_load_methods

  • 反复调用类的+load,直到不再有
  • 调用一次分类的+load
  • 如果有类或更多未尝试的分类,则运行更多的+load
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
        /// 如果有类或更多未尝试的分类,则运行更多的load方法
    } 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_method_t load_method = (load_method_t)classes[i].method;
        if (!cls) continue; 

        if (PrintLoading) {
            _objc_inform("LOAD: +[%s load]\n", cls->nameForLogging());
        }
        (*load_method)(cls, @selector(load));
    }
    
    // Destroy the detached list.
    if (classes) free(classes);
}

call_category_loads

主要是加载一次分类的load方法

load_images流程图

image.png

unmap_image

卸载数据

/***********************************************************************
* unmap_image
* Process the given image which is about to be unmapped by dyld.
*
* Locking: write-locks runtimeLock and loadMethodLock
**********************************************************************/
void 
unmap_image(const char *path __unused, const struct mach_header *mh)
{
    recursive_mutex_locker_t lock(loadMethodLock);
    mutex_locker_t lock2(runtimeLock);
    unmap_image_nolock(mh);
}

问题先行解答

1
Xcode项目的环境变量可以通过target->Edit Scheme->Run->Arguments->Environment Variables配置

  • DYLD_PRINT_ENV:打印所有环境变量
  • DYLD_PRINT_STATISTICS:设置 DYLD_PRINT_STATISTICS 为YES,控制台就会打印App的加载时长,包括整体加载时长和动态库加载时长,即main函数之前的启动时间(查看pre-main耗时),可以通过设置了解其耗时部分,并对其进行启动优化【经测试在最新macos系统无效】
  • OBJC_DISABLE_NONPOINTER_ISA:杜绝生成相应的nonpointer isanonpointer isa指针地址末尾为1),生成的都是普通的isa
  • OBJC_PRINT_LOAD_METHODS:打印 ClassCategory+ (void)load 方法的调用信息
  • NSDoubleLocalizedStrings:项目做国际化本地化(Localized)的时候是一个挺耗时的工作,想要检测国际化翻译好的语言文字UI会变成什么样子,可以指定这个启动项.可以设置NSDoubleLocalizedStringsYES
  • NSShowNonLocalizedStrings:在完成国际化的时候,偶尔会有一些字符串没有做本地化,这时就可以设置NSShowNonLocalizedStringsYES,所有没有被本地化的字符串全都会变成大写

1、详情见官方文档developer.apple.com/library/arc…
2、通过终端命令export OBJC_HELP = 1,打印环境变量 2
原理见exception_init分析,具体实现如下

#import "ASUncaughtExceptionHandler.h"

/// 记录之前的崩溃回调函数
static NSUncaughtExceptionHandler *previousUncaughtExceptionHandler = NULL;

@implementation ASUncaughtExceptionHandler

+ (void)registerHandler {
    /// 将先前别人注册的handler取出并备份
    previousUncaughtExceptionHandler = NSGetUncaughtExceptionHandler();
    /// 设置自己的异常接受处理handler
    NSSetUncaughtExceptionHandler(&UncaughtExceptionHandler);
}

static void UncaughtExceptionHandler(NSException *exception) {
    /// 异常的堆栈信息
    NSArray *stackArray = [exception callStackSymbols];
    /// 出现异常的原因
    NSString *resaon = [exception reason];
    /// 异常名称
    NSString *name = [exception name];
    /// 汇总信息
    NSString *exceptionInfo = [NSString stringWithFormat:@"======uncaughtException异常错误报告======\nname:%@\nresaon:%@\ncallStackSymbols:%@\n",name,resaon,[stackArray componentsJoinedByString:@"\n"]];
    NSLog(@"%@",exceptionInfo);
    /// 自己的handler处理完之后把别人的handler注册回去,继续传递
    if (previousUncaughtExceptionHandler) {
        previousUncaughtExceptionHandler(exception);
    }
}

@end

3
简单点讲,实现了+load方法的类是非懒加载类,否则就是懒加载类。因为load会提前加载(load方法会在load_images调用,前提是类存在)

4
见上补充-懒加载和非懒加载类初始化时机