在之前的文章中iOS应用程序加载流程主要讲述了 dyld
的加载流程,说到 dyld
在加载中会调用 _objc_init
,那么它是如何于 objc
关联的呢?下面进入今天的探究
_objc_init 源码分析
首先,我们打开 objc-781
源码,找到 _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(); //线程 key 的绑定
static_init(); //C++ 静态构造函数的调用
runtime_init(); //运行时的初始化
exception_init(); //初始化异常处理
cache_init(); //缓存的初始化
_imp_implementationWithBlock_init(); //对 imp 的 block 标记初始化
_dyld_objc_notify_register(&map_images, load_images, unmap_image); //注册处理程序,以便在映射、取消映射 和初始化objc镜像文件时使用
#if __OBJC2__
didCallDyldNotifyRegister = true;
#endif
}
由以上源码可以看到,_objc_init
主要分为以下几部分
environ_init 环境变量初始化
此函数用于读取运行时的环境变量,如果需要,可以打印环境变量。我们先来看下简化后的源码,如下
/***********************************************************************
* environ_init
* Read environment variables that affect the runtime.
* Also print environment variable help, if requested.
**********************************************************************/
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.
return;
}
bool PrintHelp = false;
bool PrintOptions = false;
bool maybeMallocDebugging = false;
// Scan environ[] directly instead of calling getenv() a lot.
// This optimizes the case where none are set.
for (char **p = *_NSGetEnviron(); *p != nil; p++) {...}
// 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.
if (maybeMallocDebugging) {...}
// Print OBJC_HELP and OBJC_PRINT_OPTIONS output.
if (PrintHelp || PrintOptions) {...}
}
从源码可以知道,核心的代码在 for
循环这段代码中,它的源码实现如下
for (char **p = *_NSGetEnviron(); *p != nil; p++) {
if (0 == strncmp(*p, "Malloc", 6) || 0 == strncmp(*p, "DYLD", 4) ||
0 == strncmp(*p, "NSZombiesEnabled", 16))
{
maybeMallocDebugging = true;
}
if (0 != strncmp(*p, "OBJC_", 5)) continue;
if (0 == strncmp(*p, "OBJC_HELP=", 10)) {
PrintHelp = true;
continue;
}
if (0 == strncmp(*p, "OBJC_PRINT_OPTIONS=", 19)) {
PrintOptions = true;
continue;
}
const char *value = strchr(*p, '=');
if (!*value) continue;
value++;
for (size_t i = 0; i < sizeof(Settings)/sizeof(Settings[0]); i++) {
const option_t *opt = &Settings[I];
if ((size_t)(value - *p) == 1+opt->envlen &&
0 == strncmp(*p, opt->env, opt->envlen))
{
*opt->var = (0 == strcmp(value, "YES"));
break;
}
}
}
通过注释和源码可知主要进行 environ
进行扫码,这样可以优化未设置的情况。中间的源码是对特殊情况的处理,这里就不过多的讲解了。我们重点看下第三部分简化的源码,主要针对环境变量的打印输出,我们可以将里面的 for 循环
拿出来,不添加条件,运行源码强行的打印所有环境变量,如下
此外,还可以通过终端命令,打印一个项目的所有环境变量
//1. cd 到任意一个项目的根目录
//2. 运行终端命令
export OBJC_hrlp = 1
以上这些环境变量,都可以通过 Xcode -> Product -> Scheme -> Edit Scheme... -> Run -> Arguments -> Environment Variables
来配置,举几个经常使用的环境变量
- DYLD_PRINT_STATISTICS
设置为 YES
,控制台就会打印 App 加载时长(pre-main 耗时)
- OBJC_DISABLE_NONPOINTER_ISA
杜绝生成相应的 nonpointer isa
(nonpointer isa
指针地址末尾为 1 ),生成的都是普通的 isa
- OBJC_PRINT_LOAD_METHODS
打印 Class
及 Category
的 + (void)load
方法的调用信息
OBJC_DISABLE_NONPOINTER_ISA 环境变量
下面我们分别打印配置该环境变量与不配置该环境变量有什么不同,首先我们设置 OBJC_DISABLE_NONPOINTER_ISA
的 Value
为 YES
- 添加如下代码,运行,打印
isa
由打印结果可知,当前 isa
的最后一位为 0(未做优化的 isa
)
- 将该环境变量删除,再重新运行并打印
由打印结果可知,当前 isa
的最后一位为 1(已做优化的 isa
)
tls_init 线程key的绑定
主要是 本地线程池
的初始化以及析构,源码如下
void tls_init(void)
{
#if SUPPORT_DIRECT_THREAD_KEYS // 本地线程池,用来进行处理
pthread_key_init_np(TLS_DIRECT_KEY, &_objc_pthread_destroyspecific); // 初始init
#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 运行时的初始化
这一部分主要是运行时的初始化,分为 分类的初始化
和 已经创建的类的初始化
(后续会展开分析,这里就不做详细讲解了)。源码如下
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);
}
程序异常即我们常说的 crash
,是指程序的代码错误和发生了系统不允许的一些指令,然后系统会给的一些信号,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.
**********************************************************************/
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); // oc 对象,抛出异常
(*old_terminate)();
} @catch (...) {
// It's not an objc object. Continue to C++ terminate.
(*old_terminate)();
}
}
}
此时,我们想跟进 uncaught_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)
{
// fn为设置的异常句柄 传入的函数,为外界给的
objc_uncaught_exception_handler result = uncaught_handler;
uncaught_handler = fn;
return result;
}
在应用程序中传入一个用于处理异常的函数(即源码只能够的 fn
),调用 objc_setUncaughtExceptionHandler
后,然后把异常信息回调到 App
。
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 启动回调机制
通常这不会做什么,因为所有的初始化都是惰性的,但是对于某些进程,我们会迫不及待地加载 libobjc-trampolines.dylib
,其源码如下
/// 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 dyld 注册通知回调
在之前的文章 iOS应用程序加载流程 介绍过这个方法了,它的源码实现是在 dyld
源码中,objc
源码中只有针对它的声明,如下
//
// 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
运行时使用 - 注册处理程序,以便要在映射、取消映射和初始化objc映像时调用
-
Dyld
会通过一个包含objc-image-info
镜像文件的数组回调mapped
函数
dyld 与 objc 的关联
在上面的 _objc_init
源码分析中我们知道最终会调用 _dyld_objc_notify_register
函数,而该函数是在 dyld
源码中实现,我们打开 dyld-750.6
源码,实现如下
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);
}
结合上面 _objc_init
的源码,我们可以得出以下结论
- mapped 等价于 map_images( dyld 将 image(镜像文件)加载进内存时,会触发该函数)
- init 等价于 load_images( dyld 初始化 image(镜像文件)会触发该函数)
- unmapped 等价于 unmap_image( dyld 将 image(镜像文件)移除时,会触发该函数)
我们再进入 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
}
// <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
就是_objc_init
源码中 调用方法_dyld_objc_notify_register
的第一个参数&map_images
(映射镜像文件) -
sNotifyObjCInit
就是第二个参数load_images
(加载镜像文件) -
sNotifyObjCUnmapped
就是第三个参数unmap_image
()
在 registerObjCNotifiers
源码中我们看到了 sNotifyObjCInit
的调用,那么 sNotifyObjCMapped
是在什么时候调用的呢?
map_images 的调用时机
既然在源码中我们没有看到 sNotifyObjCMapped
,那我们就全局搜索它在哪里调用了,在搜索结果中,只有 notifyBatchPartial
方法中调用了,如下
再次全局搜索 notifyBatchPartial
哪里调用了,在 registerObjCNotifiers
源码中找到了它的调用
由此也可以证明 map_images
先于 load_images
调用(先 map_images
后 load_images
)
在 dyld 中注册回调函数,可以理解为添加观察者
在 objc 中注册 dyld,可以理解为发送通知
触发回调,可以理解为执行通知的方法
dyld
与 objc
的关联示意图如下
网友评论