引入
在之前dyld链接篇中我们探讨了dyld是怎么链接到objc,以及初始化镜像文件后做了回调通知,在doInitialization初始化libsystem,到后面的[objc init],这篇文章将探究[objc init]做了哪些东西?
准备工作
dyld_objc_notify_register注册回调函数
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
}
_dyld_objc_notify_register中有三个参数map_images,load_images,unmap_imagemap_images内容过多我们后面分析,先分析load_images.
load_images
-
类的
load比分类的load方法先调用,类中load方法调用完才开始调用分类的load方法 -
分类
load方法 谁先编译谁先执行。 -
load_images这里只是当成参数,但在哪里调用我们跟进下。前一篇文章中我们知道,由dyld初始化libsystem->_objc_init->_dyld_objc_notify_register,那么dyld在哪里初始化libsystem了?doModInitFunctionsdyld的这个函数,这个函数之后执行了context.notifySingle,在notifSingle里面调用了*sNotifyObjCInit并且把mach-o地址,和mach-o传递进去。等于_dyld_objc_notify_register参数的load_images调用。
environ_init
读取影响运⾏时的环境变量。如果需要,还可以打印环境变量帮助
环境变量 OBJC_DISABLE_NONPOINTER_ISA
上面的代码只能在objc源码中运行才可以,如果没有objc源码,可以在终端显示 export OBJC_HELP = 1
Xcode中环境变量配置的位置:选中运行的target--> Edit scheme... --> Run --> Arguments --> Environment Variables
- 项目跑在objc源码,真实项目会强制
OBJC_DISABLE_NONPOINTER_ISA=NO; isa低位0号位是0,表示是isa是存指针,而且高位除了cls也没有其他的数据
环境变量 OBJC_PRINT_LOAD_METHODS
环境变量OBJC_PRINT_LOAD_METHODS打印出程序中所有的load方法,在自定义类中添加load方法,配置环境变量OBJC_PRINT_LOAD_METHODS = YES
load方法太多会导致你应用程序启动变慢,你可以通过这个环境变量检查所有的load方法。
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
}
tls_init关于线程key的绑定,比如每个线程数据的析构函数
static_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]();
}
auto offsets = getLibobjcInitializerOffsets(&_mh_dylib_header, &count);
for(size_t i = 0; i < count; i++) {
UnsignedInitializer init(offsets[i]);
init();
}
}
运行C++静态构造函数。在dyld调用我们的静态构造函数之前,lib会调用_objc_init先调用自己的C++构造函数,简单说的就是libobjc会调用自己的全局的C++函数,因为比较重要所以比dyld先调用
runtime_init
void runtime_init(void)
{
objc::unattachedCategories.init(32);//分类表的初始化
objc::allocatedClasses.init();//类表的初始化
}
exception_init
void exception_init(void)
{
old_terminate = std::set_terminate(&_objc_terminate);
}
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 {
// 当前存在异常。 检查它是否是 objc 异常。
// 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这个函数的参数中。
-
我们可以看出系统将崩溃信息通过
objc_setUncaughtExceptionHandler的参数fn赋值给uncaught_handler。 -
objc_setUncaughtExceptionHandler它是runtime的实现方式。它在oc层面实现函数是NSSetUncaughtExceptionHandler,我们可以在oc层实现这个函数,在写个方法将exception接收过来就能实现crash收集工作。
- 只需要注册
installUncaughtSignalExceptionHandler就能收集崩溃信息
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
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
}
- 启动回调机制。通常不会做什么,因为所有的初始化都是惰性的,但是对于某些进程,会迫不及待的加载
trampolines dylib
_dyld_objc_notify_register
// _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);
}
// _dyld_objc_notify_init
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<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());
}
}
}
- 前面探究了
load_images方法 其实就是调用load方法,现在探究map_images方法,&map_images是指针传递,指向是同一块实现的地址,因为&map_images是第一个参数,在dyld中全局搜索sNotifyObjCMapped
- 它在
notifyBatchPartial中有调用。 notifyBatchPartial在registerObjCNotifiers中有调用。
- 所以是先执行
&map_images,然后在执行load_images