接下来让我们一起探索dyld和objc的关联
首先查看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();
cache_init();
_imp_implementationWithBlock_init();
// 什么时候调用? images 镜像文件
// map_images()
// load_images()
_dyld_objc_notify_register(&map_images, load_images, unmap_image);
#if __OBJC2__
didCallDyldNotifyRegister = true;
#endif
}
- environ_init():读取影响
运行时
的环境变量
,如有需要,还可以打印环境变量帮助
- tls_init():关于线程
key
的绑定,比如每线程数据的析构函数
- static_init():运行C++静态构造函数,在dyld调用我们的静态构造函数之前,
libc
会调用_objc_init()
,因此我们必须自己做 - lock_init():没有
重写
,采用C++
的特性 - exception_init (): 初始化
libobjc
的异常处理系统 - runtime_init() :
runtime
运行时环境初始化,里面主要是:unattachedCategories
,allocatedClasses
- cache_init():缓存条件初始化
- _imp_implementationWithBlock_init :
启动回调机制
。通常这不会做什么,因为所有的初始化都是惰性的
,但是对于某些进程,我们会迫不及待地加载trampolines dylib
。
environ_init源码
我们可以在PrintHelp
这里查看环境变量
,但是由于一些条件限制
,无法打印环境变量
,那么我们可以将for循环
单独写出来进行打印。
//打印环境变量
// for (size_t i = 0; i < sizeof(Settings)/sizeof(Settings[0]); i++) {
// const option_t *opt = &Settings[i];
// _objc_inform("%s: %s", opt->env, opt->help);
// _objc_inform("%s is set", opt->env);
// }
// 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);
}
}
打印结果
也可以通过终端进行打印输入:export OBJC_hrlp=1
,打印环境变量
这些环境变量,均可以通过target -- Edit Scheme -- Run --Arguments -- Environment Variables
配置
- DYLD_PRINT_STATISTICS:设置
DYLD_PRINT_STATISTICS
为YES
,控制台就会打印 App 的加载时长,包括整体加载时长和动态库加载时长,即main函数之前的启动时间(查看pre-main耗时),可以通过设置了解其耗时部分,并对其进行启动优化。 - 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
,所有没有被本地化的字符串全都会变成大写。
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 void static_init()
{
size_t count;
auto inits = getLibobjcInitializers(&_mh_dylib_header, &count);
for (size_t i = 0; i < count; i++) {
inits[i]();
}
}
runtime_init()源码
runtime
运行时环境初始化,里面主要是:unattachedCategories
,allocatedClasses
(后面会分析)
void runtime_init(void)
{
objc::unattachedCategories.init(32);
objc::allocatedClasses.init();
}
exception_init()源码
初始化libobjc
的异常处理系统
,注册异常处理的回调,从而监控异常的处理
void exception_init(void)
{
old_terminate = std::set_terminate(&_objc_terminate);
}
当有crash(系统发生的不允许的一些指令,然后系统给的一些信号)发生时,会进入_objc_terminate
方法,走到uncaught_handler
扔出异常
static void _objc_terminate(void)
{
if (PrintExceptions) {
_objc_inform("EXCEPTIONS: terminating");
}
if (! __cxa_current_exception_type()) {
// No current exception.
(*old_terminate)();
}
else {
// There is a current exception. Check if it's an objc exception.
@try {
__cxa_rethrow();
} @catch (id e) {
// It's an objc object. Call Foundation's handler, if any.
(*uncaught_handler)((id)e);
(*old_terminate)();
} @catch (...) {
// It's not an objc object. Continue to C++ terminate.
(*old_terminate)();
}
}
}
搜索uncaught_handler
app层传入的函数,用于处理异常
,以便调用函数
objc_setUncaughtExceptionHandler(objc_uncaught_exception_handler fn)
{
//fn外界传入的函数
objc_uncaught_exception_handler result = uncaught_handler;
uncaught_handler = fn;
return result;
}
crash分类
crash
的主要原因是收到了未处理的信号
,主要来源于三个地方:
- kernel 内核
- 其他进行
- App本身
crash也分为了3种:
Mach异常:是指最底层的内核级异常。用户态的开发者可以直接通过
Mach API
设置thread,task,host
的异常端口,来捕获Mach
异常。Unix信号:又称
BSD
信号,如果开发者没有捕获Mach异常
,则会被host
层的方法ux_exception()
将异常转换为对应的UNIX
信号,并通过方法threadsignal()
将信号投递到出错线程
。可以通过方法signal(x, SignalHandler)
来捕获single
。NSException 应用级异常:它是未被捕获的
Objective-C
异常,导致程序向自身发送了SIGABRT
信号而崩溃,对于未捕获的Objective-C
异常,是可以通过try catch
来捕获的,或者通过NSSetUncaughtExceptionHandler()
机制来捕获。
针对应用级异常
,可以通过注册异常捕获的函数,即NSSetUncaughtExceptionHandler
机制,实现线程保活, 收集上传崩溃日志
cache_init()源码
缓存条件初始化
void cache_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()源码
启动回调机制
。通常这不会做什么,因为所有的初始化都是惰性
的,但是对于某些进程,我们会迫不及待地加载trampolines dylib
_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()源码
void _dyld_objc_notify_register(_dyld_objc_notify_mapped mapped,
_dyld_objc_notify_init init,
_dyld_objc_notify_unmapped unmapped);
方法中的三个参数分别表示的含义如下:
- map_images:
应用类型,dyld
将image
(镜像文件)映射进内存时,会触发该函数 - load_image:
值类型,dyld
初始化image
会触发该函数 - unmap_image:
dyld
将image
移除时,会触发该函数
探索什么时候调用map_images和load_image
dyld与Objc的关联可以通过源码体现
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);
}
可以看出,load_images
是在notifySingle
方法中,通过sNotifyObjCInit
调用的,如图所示
通过_dyld_objc_notify_register --> registerObjCNotifiers
在该方法中将_dyld_objc_notify_register
传入的参数赋值给了3个回调方法
它们的关系
sNotifyObjCMapped == mapped == map_images
sNotifyObjCInit == init == load_images
sNotifyObjCUnmapped == unmapped == unmap_image
map_images调用时机
dyld
中全局搜索 sNotifyObjcMapped
:registerObjCNotifiers -- notifyBatchPartial -- sNotifyObjCMapped
全局搜索notifyBatchPartial
,在registerObjCNotifiers
方法中调用
结论:map_images
是先于load_images
调用