iOS底层探索 _objc_init

1,410 阅读6分钟

前言

在iOS动态链接过程中,_objc_init起了非常重要的作用,因为_objc_init向dyld动态库中注册了回调函数。跟随源码看一下_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();

#if __OBJC2__
    cache_t::init();
#endif
    _imp_implementationWithBlock_init();
    _dyld_objc_notify_register(&map_images, load_images, unmap_image);
    // map_images()
    // load_images()

#if __OBJC2__
    didCallDyldNotifyRegister = true;
#endif
}

大致介绍下流程:
environ_init读取影响运行时的环境变量,如果需要还可以打印,如:xcode中配置OBJC_PRINT_LOAD_METHODS
tls_init作用:1、线程局部存储,因为有的一些变量是存储在线程的栈中 2、线程key的绑定,线程的析构函数 static_init运行C++静态构造函数,在dyld调用我们的静态构造函数之前,libobjc内部如果有自己的构造函数需要做准备工作,则在这里就会调用,而不用等着dyld。
runtime_init运行环境初始化,创建两张表 unattachedCategories & allocatedClasses
exception_init初始化objc库的异常处理系统
cache_t::init缓存条件初始化
_imp_implementationWithBlock_init启动回调机制,通常不会什么,因为所有的初始化都是惰性的,但是对于某些进程,会迫不及待的加载
_dyld_objc_notify_register向dyld注册回调通知

environ_init

void environ_init() {
//此处省略部分内容...
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);
        }
    }
}

当我们在xcode中配置环境变量后,在这里就会打印出配置的内容,如下:

image.png

image.png load方法使用的多会加长程序的启动时间,当你需要排出时,在xcode中直接配置环境变量打印,可直接查询

tls_init

void tls_init(void) {
#if SUPPORT_DIRECT_THREAD_KEYS
    pthread_key_init_np(TLS_DIRECT_KEY, &_objc_pthread_destroyspecific);
#else
    //线程key析构函数
    _objc_pthread_key = tls_create(&_objc_pthread_destroyspecific);
#endif
}

static_init

运行C++构造函数,。在dyld调用我们的静态构造函数之前,lib会调用_objc_init先调用自己的C++构造函数,简单说的就是libobjc会调用自己的全局的C++函数,因为比较重要所以比dyld先调用

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

分类表的初始化 & 类表的初始化,allocated指的是已开辟的

void runtime_init(void)
{
    objc::unattachedCategories.init(32);
    objc::allocatedClasses.init();
}

exception_init

初始化异常处理系统,当程序出现异常时,用户可以自己去获取异常信息回调,可以上报服务器或其他

 void  exception_init(void)
 {
  old_terminate = std::set_terminate(&_objc_terminate);
 }

顺着源码去找,会找到如下回调处理

objc_uncaught_exception_handler 
objc_setUncaughtExceptionHandler(objc_uncaught_exception_handler fn)
{
    objc_uncaught_exception_handler result = uncaught_handler;
    uncaught_handler = fn;
    return result;
}

对应OC代码处理如下, 这些代码可以封装在一个类里,程序启动之后,就可以调用:

+ (void)installUncaughtSignalExceptionHandler {
//  objc_setUncaughtExceptionHandler()
    NSSetUncaughtExceptionHandler(&exceptionHandlers);
}

// Exception 在这里可以做对应的处理,这就好比是block的回调
void exceptionHandlers(NSException *exception) {
    NSLog(@"%s",__func__);
    int32_t exceptionCount = atomic_fetch_add_explicit(&LGUncaughtExceptionCount,1,memory_order_relaxed);
    if (exceptionCount > LGUncaughtExceptionMaximum) {
        return;
    }

    // 获取堆栈信息 - model 编程思想
    NSArray *callStack = [LGUncaughtExceptionHandle lg_backtrace];
    NSMutableDictionary *userInfo = [NSMutableDictionary dictionaryWithDictionary:[exception userInfo]];
    [userInfo setObject:exception.name forKey:LGUncaughtExceptionHandlerSignalExceptionName];
    [userInfo setObject:exception.reason forKey:LGUncaughtExceptionHandlerSignalExceptionReason];
    [userInfo setObject:callStack forKey:LGUncaughtExceptionHandlerAddressesKey];
       }

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

dyld的注册回调,_dyld_objc_notify_register仅供objc运行时调用且方法的实现在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);
}

回调句柄赋值

// _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());
        }
      }
}

//调用时机:xcode打打点符号map_images,运行就可发现调用栈,mapImages是在notifyBatchPartial方法中,而notifyBatchPartial方法是在registerObjCNotifiers调用,在objc初始化注册通知时就调用了,所以是调用map_images后调用load_images

map_images

打来源码libobjc,查询mag_images,看到如下代码

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);
}

进入map_images_nolock,代码较多

image.png 大致流程:计算class数量,调整各种表的大小,初始化sel方法表,重点则为:_read_images,直接看_read_images方法

_read_images

void _read_images(header_info **hList, uint32_t hCount, int 
totalClasses, int 
unoptimizedTotalClasses)
{
   ... //表示省略部分代码
#define EACH_HEADER \
    hIndex = 0;         \
    hIndex < hCount && (hi = hList[hIndex]); \
    hIndex++
    // 条件控制进行一次的加载
    if (!doneOnce) { ... }
    // 修复预编译阶段的`@selector`的混乱的问题
    // 就是不同类中有相同的方法 但是相同的方法地址是不一样的
    // Fix up @selector references
    static size_t UnfixedSelectors;
    { ... }
    ts.log("IMAGE TIMES: fix up selector references");
    
    // 错误混乱的类处理
    // Discover classes. Fix up unresolved future classes. Mark bundle classes.
    bool hasDyldRoots = dyld_shared_cache_some_image_overridden();
    for (EACH_HEADER) { ... }
    ts.log("IMAGE TIMES: discover classes");
    
    // 修复重映射一些没有被镜像文件加载进来的类
    // Fix up remapped classes
    // Class list and nonlazy class list remain unremapped.
    // Class refs and super refs are remapped for message dispatching.
    if (!noClassesRemapped()) { ... }
    ts.log("IMAGE TIMES: remap classes");

#if SUPPORT_FIXUP
    // 修复一些消息
    // Fix up old objc_msgSend_fixup call sites
    for (EACH_HEADER) { ... }
    ts.log("IMAGE TIMES: fix up objc_msgSend_fixup");

#endif
    // 当类中有协议时:`readProtocol`
    // Discover protocols. Fix up protocol refs.
    for (EACH_HEADER) { ... }
    ts.log("IMAGE TIMES: discover protocols");
    
    // 修复没有被加载的协议
    // Fix up @protocol references
    // Preoptimized images may have the right 
    // answer already but we don't know for sure.
    for (EACH_HEADER) { ... }
    ts.log("IMAGE TIMES: fix up @protocol references");
    
    // 分类的处理
    // 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.  
    if (didInitialAttachCategories) { ... }
    ts.log("IMAGE TIMES: discover categories");
    
    // 类的加载处理
    // Category discovery MUST BE Late to avoid potential races
    // when other threads call the new category code befor
    // this thread finishes its fixups.
    // +load handled by prepare_load_methods()
    // Realize non-lazy classes (for +load methods and static instances)
    for (EACH_HEADER) { ... }
    ts.log("IMAGE TIMES: realize non-lazy classes");
    
    // 没有被处理的类,优化那些被侵犯的类
    // Realize newly-resolved future classes, in case CF manipulates them
    if (resolvedFutureClasses) { ... }
    ts.log("IMAGE TIMES: realize future classes");
   ...
#undef EACH_HEADER

}

主要流程:

  • 条件控制运行一次加载
  • 读取方法列表,修复预编译阶段的@selector混乱问题,就是不同类中,有相同的方法,但是相同的方法地址不一样,因为类的地址是不一样的
  • 错误混乱的类处理
  • 修复重映射一些没有北京向文件加载进来的类
  • 修复一些休息
  • 读取协议 readProtocol
  • 分类的处理
  • 类的加载处理,非懒加载的类,读取类的信息,加载的类表中(realizeClassWithoutSwift),懒加载的类,第一次发送消息之前加到表中

错误混乱的类处理

for (EACH_HEADER) {
    if (! mustReadClasses(hi, hasDyldRoots)) {
        // Image is sufficiently optimized that we need not call readClass()
        continue;
    }
    //从macho中读取类列表信息
    classref_t const *classlist = _getObjc2ClassList(hi, &count);
    bool headerIsBundle = hi->isBundle();
    bool headerIsPreoptimized = hi->hasPreoptimizedClasses();

    for (i = 0; i < count; i++) {
        Class cls = (Class)classlist[i];
        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

Class readClass(Class cls, bool headerIsBundle, bool headerIsPreoptimized)
{
    // 获取类名
    const char *mangledName = cls->nonlazyMangledName();
    if (missingWeakSuperclass(cls)) { ... }
    cls->fixupBackwardDeployingStableSwift();
    Class replacing = nil;

    if (mangledName != nullptr) { ... }

    if (headerIsPreoptimized  &&  !replacing) {...
    } else {
        if (mangledName) { 
        //some Swift generic classes can lazily generate their names
            //将类名和地址关联起来
            addNamedClass(cls, mangledName, replacing);
        } else { ...}
        //将关联的类插入到另一张哈希表中
        addClassTableEntry(cls);
    }
    // for future reference: shared cache never contains MH_BUNDLEs
    if (headerIsBundle) { ... }
    return cls;

}
  • 代码中发现了rw的赋值和ro的获取,但是通过调试过程,发现这块并不走。
  • addNamedClass将类名和地址关联绑定起来
  • addClassTableEntry将关联的类插入到allocatedClasses表中,这张表中都是初始化过的类 以上的代码和下面的MACH-O一一对应

image.png 这个可以在readclass中打断点验证,地址和MACH-O文件中的一样 image.png

non-lazy classes加载

image.png 注释意思很明显,实现了load方法的类,就会走realizeClassWithoutSwift方法,没有实现的默认跳过,这个是和MACH-O文件相对应的,如下图

iOS底层探索 _objc_init & readImages

前言

在iOS动态链接过程中,_objc_init起了非常重要的作用,因为_objc_init向dyld动态库中注册了回调函数。跟随源码看一下_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();

#if __OBJC2__
    cache_t::init();
#endif
    _imp_implementationWithBlock_init();
    _dyld_objc_notify_register(&map_images, load_images, unmap_image);
    // map_images()
    // load_images()

#if __OBJC2__
    didCallDyldNotifyRegister = true;
#endif
}

大致介绍下流程:
environ_init读取影响运行时的环境变量,如果需要还可以打印,如:xcode中配置OBJC_PRINT_LOAD_METHODS
tls_init作用:1、线程局部存储,因为有的一些变量是存储在线程的栈中 2、线程key的绑定,线程的析构函数 static_init运行C++静态构造函数,在dyld调用我们的静态构造函数之前,libobjc内部如果有自己的构造函数需要做准备工作,则在这里就会调用,而不用等着dyld。
runtime_init运行环境初始化,创建两张表 unattachedCategories & allocatedClasses
exception_init初始化objc库的异常处理系统
cache_t::init缓存条件初始化
_imp_implementationWithBlock_init启动回调机制,通常不会什么,因为所有的初始化都是惰性的,但是对于某些进程,会迫不及待的加载
_dyld_objc_notify_register向dyld注册回调通知

environ_init

void environ_init() {
//此处省略部分内容...
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);
        }
    }
}

当我们在xcode中配置环境变量后,在这里就会打印出配置的内容,如下:

image.png

image.png load方法使用的多会加长程序的启动时间,当你需要排出时,在xcode中直接配置环境变量打印,可直接查询

tls_init

void tls_init(void) {
#if SUPPORT_DIRECT_THREAD_KEYS
    pthread_key_init_np(TLS_DIRECT_KEY, &_objc_pthread_destroyspecific);
#else
    //线程key析构函数
    _objc_pthread_key = tls_create(&_objc_pthread_destroyspecific);
#endif
}

static_init

运行C++构造函数,。在dyld调用我们的静态构造函数之前,lib会调用_objc_init先调用自己的C++构造函数,简单说的就是libobjc会调用自己的全局的C++函数,因为比较重要所以比dyld先调用

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

分类表的初始化 & 类表的初始化,allocated指的是已开辟的

void runtime_init(void)
{
    objc::unattachedCategories.init(32);
    objc::allocatedClasses.init();
}

exception_init

初始化异常处理系统,当程序出现异常时,用户可以自己去获取异常信息回调,可以上报服务器或其他

 void  exception_init(void)
 {
  old_terminate = std::set_terminate(&_objc_terminate);
 }

顺着源码去找,会找到如下回调处理

objc_uncaught_exception_handler 
objc_setUncaughtExceptionHandler(objc_uncaught_exception_handler fn)
{
    objc_uncaught_exception_handler result = uncaught_handler;
    uncaught_handler = fn;
    return result;
}

对应OC代码处理如下, 这些代码可以封装在一个类里,程序启动之后,就可以调用:

+ (void)installUncaughtSignalExceptionHandler {
//  objc_setUncaughtExceptionHandler()
    NSSetUncaughtExceptionHandler(&exceptionHandlers);
}

// Exception 在这里可以做对应的处理,这就好比是block的回调
void exceptionHandlers(NSException *exception) {
    NSLog(@"%s",__func__);
    int32_t exceptionCount = atomic_fetch_add_explicit(&LGUncaughtExceptionCount,1,memory_order_relaxed);
    if (exceptionCount > LGUncaughtExceptionMaximum) {
        return;
    }

    // 获取堆栈信息 - model 编程思想
    NSArray *callStack = [LGUncaughtExceptionHandle lg_backtrace];
    NSMutableDictionary *userInfo = [NSMutableDictionary dictionaryWithDictionary:[exception userInfo]];
    [userInfo setObject:exception.name forKey:LGUncaughtExceptionHandlerSignalExceptionName];
    [userInfo setObject:exception.reason forKey:LGUncaughtExceptionHandlerSignalExceptionReason];
    [userInfo setObject:callStack forKey:LGUncaughtExceptionHandlerAddressesKey];
       }

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

dyld的注册回调,_dyld_objc_notify_register仅供objc运行时调用且方法的实现在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);
}

回调句柄赋值

// _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());
        }
      }
}

//调用时机:xcode打打点符号map_images,运行就可发现调用栈,mapImages是在notifyBatchPartial方法中,而notifyBatchPartial方法是在registerObjCNotifiers调用,在objc初始化注册通知时就调用了,所以是调用map_images后调用load_images