iOS开发 - 程序启动流程探究
上篇文章我们分析了一遍dyld
的流程iOS开发 - 程序加载过程之dyld流程分析,这篇文章我们就来探究探究dyld
和objc
之间的关联
。
objc_init 源码分析
可调式源码全局搜索objc_init(
,查看源码如下:
void _objc_init(void)
{
static bool initialized = false;
if (initialized) return;
initialized = true;
// fixme defer initialization until an objc-using image is found?
environ_init();
tls_init();
static_init();
runtime_init();
exception_init();
cache_init();
_imp_implementationWithBlock_init();
_dyld_objc_notify_register(&map_images, load_images, unmap_image);
#if __OBJC2__
didCallDyldNotifyRegister = true;
#endif
}
阅读源码可知,objc_init()
中进行了如下的一系列操作:
-
environ_init():
读取影响运行时的环境变量
-
tls_init():
关于线程key
的绑定 - 比如每个线程数据的析构函数
-
static_init():
运行C++ 静态构造函数
(系统级别
的构造函数)。在dyld
调用我们的静态构造函数之前,libc
会调用_objc_init()
-
runtime_init():
Runtime
环境初始化,unattachedCategories
和allocatedClasses
两张表的初始化工作 -
exception_init():
初始化libobjc
的异常处理
系统 -
cache_init():
cache
缓存条件初始化 -
_imp_implementationWithBlock_init():
启动回调机制。通常不会做什么,因为所有的初始化都是惰性的,但是对于某些进程,我们会迫不及待地加载trampolines dylib
。 -
_dyld_objc_notify_register():
dyld
的注册工作
下面我们分别对所有操作进行分析:
environ_init():读取运行时环境变量
源码如下,省略一部分,关键部分为for循环打印所有环境变量:
// 省略一部分代码
// 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
为false
,不会打印结果,所以我们进行一些修改,将打印函数提出来:
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);
}
得到所有的打印结果如下:
这些环境变量,均可以通过
target -- Edit Scheme -- Run --Arguments -- Environment Variables
配置,例如我们设置一个环境变量OBJC_DISABLE_NONPOINTER_ISA
,利用lldb
调试打印对象的isa二进制打印
对比查看:
添加了
OBJC_DISABLE_NONPOINTER_ISA
变量并且设置为YES
,打印结果:
移除
OBJC_DISABLE_NONPOINTER_ISA
环境变量后的打印结果:
对比打印结果,
isa
末尾的数字由0
变成了1
,说明OBJC_DISABLE_NONPOINTER_ISA
可以控制isa
优化开关,从而优化
整个内存结构
。
tls_init(): 关于线程key的绑定
tls_init()
操作本地线程池的初始化
及线程的析构
:
void tls_init(void)
{
#if SUPPORT_DIRECT_THREAD_KEYS
pthread_key_init_np(TLS_DIRECT_KEY, &_objc_pthread_destroyspecific);
#else
_objc_pthread_key = tls_create(&_objc_pthread_destroyspecific);
#endif
}
static_init() 运行 C++ 静态构造函数
主要是运行系统级别的C++
静态构造函数,在dyld
调用我们的静态构造函数
之前,libc
调用_objc_init
方法,即系统级别
的C++构造函数 先于 自定义
的C++构造函数运行
/***********************************************************************
* 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]();
}
}
runtime_init() Runtime环境初始化
runtime_init
主要是unattachedCategories(分类)
和allocatedClasses(类)
两个表的初始化工作:
void runtime_init(void)
{
objc::unattachedCategories.init(32);
objc::allocatedClasses.init();
}
exception_init() 初始化libobjc的异常处理系统
初始化 libobjc
的异常处理系统,注册异常处理的回调,从而监控异常的处理,源码:
/***********************************************************************
* exception_init
* Initialize libobjc's exception handling system.
* Called by map_images().
**********************************************************************/
void exception_init(void)
{
old_terminate = std::set_terminate(&_objc_terminate);
}
当程序crash
发生时,程序会来到_objc_terminate
,然后在判断造成crash
的是否是objc
的对象,如果是的话,就会调用uncaught_handler
扔出异常
/***********************************************************************
* _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)();
}
}
}
搜索uncaught_handler
,会传入一个函数用于处理处理异常,然后回传到app应用层
,如下,其中的fn
函数即为异常发生的函数
objc_uncaught_exception_handler
objc_setUncaughtExceptionHandler(objc_uncaught_exception_handler fn)
{
objc_uncaught_exception_handler result = uncaught_handler;
uncaught_handler = fn;
return result;
}
cache_init() cache缓存条件初始化
主要进行缓存cache
的初始化工作:
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
。
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 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);
从源码及注释可知,仅运行时可用
,在此注册的是objc镜像文件
的映射
、反映射
和初始化
方法,dyld
流程中会在不同时机调用_dyld_objc_notify_register
中注册的三个回调函数方法
:
-
map_images = mapped
dyld在将Mach-O中的镜像文件映射到内存中时会调用 -
load_images = init
dyld加载所有load 方法及所有的分类相关处理 -
unmap_image = unmapped
dyld 将镜像文件删除时候,会触发此方法
dyld 与 objc 关联
_dyld_objc_notify_register
方法其实是一个跨层的方法,我们在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
源码:
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
}
// call 'init' function on all images already init'ed (below libSystem)
for (std::vector::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());
}
}
}
从上面registerObjCNotifiers
源码我们可以看到,将mapped(map_images)赋值给了sNotifyObjCMapped,将init(load_images)赋值给了sNotifyObjCInit,将unmap_image(unmapped)赋值给了sNotifyObjCUnmapped,那么我们可以通过探索sNotifyObjCMapped及sNotifyObjCInit在什么时候哪里调用来探索dyld和objc之间的关联。
sNotifyObjCMapped
在 dyld
源码中搜索sNotifyObjCMapped
,知道sNotifyObjCMapped
只有在一个地方调用:
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);
}
//省略部分代码
}
全局搜索notifyBatchPartial
,发现在上面的registerObjCNotifiers
源码中在对sNotifyObjCMapped
进行赋值之后就有调用。可见map_images的
调用是在load_images
之前的。
sNotifyObjCInit
全局搜索sNotifyObjCInit
,发现在notifySingle()
处调用了,而notifySingle
方法正式我们上一篇文章分析的,镜像文件递归初始化
时候recursiveInitialization
方法中调用了,源码如下:
void ImageLoader::recursiveInitialization(const LinkContext& context, mach_port_t this_thread, const char* pathToInitialize,
InitializerTimingList& timingInfo, UninitedUpwards& uninitUps)
{
// record termination order
if ( this->needsTermination() )
context.terminationRecorder(this);
// let objc know we are about to initialize this image
uint64_t t1 = mach_absolute_time();
fState = dyld_image_state_dependents_initialized;
oldState = fState;
context.notifySingle(dyld_image_state_dependents_initialized, this, &timingInfo);
// initialize this image
bool hasInitializers = this->doInitialization(context);
// let anyone know we finished initializing this image
fState = dyld_image_state_initialized;
oldState = fState;
context.notifySingle(dyld_image_state_initialized, this, NULL);
if ( hasInitializers ) {
uint64_t t2 = mach_absolute_time();
timingInfo.addTime(this->getShortName(), t2-t1);
}
}
到这里,我们已经找到了sNotifyObjCInit
在什么时候调用,即objc_init
中注册的load_images
方法的调用,得出下方load_images
调用链关系:
_dyld_start --> dyldbootstrap::start --> dyld::_main --> dyld::initializeMainExecutable --> ImageLoader::runInitializers --> ImageLoader::processInitializers --> ImageLoader::recursiveInitialization --> dyld::notifySingle(是一个回调处理) --> sNotifyObjCInit --> load_images(libobjc.A.dylib)
那么到了这一步,还是不知道objc_init
在哪里调用,我们继续探究。
doInitialization
在上方recursiveInitialization()
方法中我们忽略了bool hasInitializers = this->doInitialization(context);
这句代码,忽略了doInitialization
方法到底做了什么,查看源码:
bool ImageLoaderMachO::doInitialization(const LinkContext& context)
{
CRSetCrashLogMessage2(this->getPath());
// mach-o has -init and static initializers
doImageInit(context);
doModInitFunctions(context);
CRSetCrashLogMessage2(NULL);
return (fHasDashInit || fHasInitializers);
}
分别分析doImageInit
和doModInitFunctions
两个方法调用:
doImageInit
中主要是for循环加载方法的调用
,我们不要关注的是libSystem
初始化程序必须先运行这个提示:
doModInitFunctions
方法中主要是加载所有C++
方法:
研究到这里,doInitialization
方法中也没发现什么实质性调用objc_init
方法的地方,只获取到一个信息,libSystem
初始化工作。那么objc_init
到底在哪里调用了呢?
我们不妨在objc_init
方法里面打下断点,来查看一下堆栈情况:
从上图
objc_init
的调用堆栈来看,加上上面一系列的研究我们到了doModInitFunctions
,然后查看堆栈,来到libSystem_initializer
方法,我们在libSystem
中搜索libSystem_initializer
:
对照objc_init
调用堆栈,及libSystem_initializer
源码,我们找到libdispatch_init()
在libSystem_initializer
中调用了,继续查看libdispatch_init
源码:
继续os_object_init()
方法源码查找:
终于我们看到了objc_init
方法的调用,功夫不负有心人,终于把它找到了。
【总结】结合上面的分析,从初始化_objc_init
注册的_dyld_objc_notify_register
的参数2,即load_images
,到sNotifySingle
--> sNotifyObjCInie=参数2
到sNotifyObjcInit()
调用,形成了一个闭环
。
那我们可以这样理解:sNotifySingle
相当于作为一个观察者observor
,_objc_init
中调用_dyld_objc_notify_register
相当于发送通知,即push
,而sNotifyObjcInit
相当于通知的处理函数,即selector
。
结论
_objc_init
的源码链:_dyld_start
--> dyldbootstrap::start
--> dyld::_main
--> dyld::initializeMainExecutable
--> ImageLoader::runInitializers
--> ImageLoader::processInitializers
--> ImageLoader::recursiveInitialization
--> doInitialization
-->libSystem_initializer
(libSystem.B.dylib) --> _os_object_init
(libdispatch.dylib) --> _objc_init
(libobjc.A.dylib)