问题先行
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
通过配置相关环境变量在控制台打印日志信息,例如:打印项目中Class及Category的+ (void)load 方法的调用信息
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_images:dyld将image(镜像文件)加载进内存时,会触发该函数 - 参数
load_image:dyld初始化image会触发该函数 - 参数
unmap_image:dyld将image移除时,会触发该函数 - 仅供
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方法。dyld的recursiveInitialization方法在调用完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方法主要功能,读取class的data数据,并设置ro、rw,递归调用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;
}
- 关于
ro,rw更多信息 - 通过
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之前这个方法在消息转发的慢速查找中有涉及,也就是说懒加载类在第一次处理消息的时候才去现实类的加载。 - 【非懒加载类】见如上类的加载流程 具体如下
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流程图
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 isa(nonpointer isa指针地址末尾为1),生成的都是普通的isa - OBJC_PRINT_LOAD_METHODS:打印
Class及Category的+ (void)load方法的调用信息 - NSDoubleLocalizedStrings:项目做国际化本地化(
Localized)的时候是一个挺耗时的工作,想要检测国际化翻译好的语言文字UI会变成什么样子,可以指定这个启动项.可以设置NSDoubleLocalizedStrings为YES - NSShowNonLocalizedStrings:在完成国际化的时候,偶尔会有一些
字符串没有做本地化,这时就可以设置NSShowNonLocalizedStrings为YES,所有没有被本地化的字符串全都会变成大写
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
见上补充-懒加载和非懒加载类初始化时机