iOS-17.dyld和objc的关联

766 阅读7分钟

ios底层文章汇总

1.程序的启动和加载流程

从上篇 iOS-16.程序启动流程 可知 底层库的加载流程为:

_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)

那么类的方法,属性,协议是在什么时候加载到macho中?\color{#FF0000}{那么类的方法,属性,协议是在什么时候加载到macho中?}

2 _objc_init 源码


void _objc_init(void)
{
    static bool initialized = false;
    if (initialized) return;
    initialized = true;
    
    //读取影响运行时的环境变量,如果需要,还可以打开环境变量帮助 export OBJC_HELP = 1
    environ_init();
    
    //关于线程key的绑定,例如线程数据的析构函数
    tls_init();
       
    //运行C++静态构造函数,在dyld调用我们的静态析构函数之前,libc会调用_objc_init(),因此我们必须自己做
    static_init();
       
    //runtime运行时环境初始化,里面主要是unattachedCategories、allocatedClasses -- 分类初始化
    runtime_init();
       
    //初始化libobjc的异常处理系统
    exception_init();
       
    //缓存条件初始化
    cache_init();
       
    //启动回调机制,通常这不会做什么,因为所有的初始化都是惰性的,但是对于某些进程,我们会迫不及待地加载trampolines dylib
    _imp_implementationWithBlock_init();

       /*
        _dyld_objc_notify_register -- dyld 注册的地方
        - 仅供objc运行时使用
        - 注册处理程序,以便在映射、取消映射和初始化objc镜像文件时使用,
        dyld将使用包含objc_image_info的镜像文件数组,回调 mapped 函数
        
        map_images:dyld将image镜像文件加载进内存时,会触发该函数
        load_images:dyld初始化image会触发该函数
        unmap_image:dyld将image移除时会触发该函数
        */
    _dyld_objc_notify_register(&map_images, load_images, unmap_image);

#if __OBJC2__
    didCallDyldNotifyRegister = true;
#endif
}

  • environ_init() : 读取影响运行时的环境变量。如果需要,还可以打印环 境变量export OBJC_HELP = 1帮助。

  • tls_init(): 关于线程key的绑定,比如每线程数据的析构函数

  • static_init(): 运行C ++静态构造函数。在dyld调用我们的静态构造函数之前,libc 会调用 _objc_init(),因此我们必须自己做

  • exception_init (): 初始化libobjc的异常处理系统

  • cache_init(): 缓存初始化

  • runtime_init() : runtime运行时环境初始化,里面主要是:unattachedCategories,allocatedClasses

  • _imp_implementationWithBlock_init :启动回调机制。通常这不会做什么,因为所有的初始化都是惰性的,但是对于某些进程,我们会迫不及待地加载trampolines dylib。

2.1 environ_init方法:环境变量初始化

修改environ_init源码,打印环境变量

objc[29397]: OBJC_PRINT_IMAGES: log image and library names as they are loaded
objc[29397]: OBJC_PRINT_IMAGES is set
objc[29397]: OBJC_PRINT_IMAGE_TIMES: measure duration of image loading steps
objc[29397]: OBJC_PRINT_IMAGE_TIMES is set
objc[29397]: OBJC_PRINT_LOAD_METHODS: log calls to class and category +load methods
objc[29397]: OBJC_PRINT_LOAD_METHODS is set
objc[29397]: OBJC_PRINT_INITIALIZE_METHODS: log calls to class +initialize methods
objc[29397]: OBJC_PRINT_INITIALIZE_METHODS is set
objc[29397]: OBJC_PRINT_RESOLVED_METHODS: log methods created by +resolveClassMethod: and +resolveInstanceMethod:
objc[29397]: OBJC_PRINT_RESOLVED_METHODS is set
objc[29397]: OBJC_PRINT_CLASS_SETUP: log progress of class and category setup
objc[29397]: OBJC_PRINT_CLASS_SETUP is set
objc[29397]: OBJC_PRINT_PROTOCOL_SETUP: log progress of protocol setup
objc[29397]: OBJC_PRINT_PROTOCOL_SETUP is set
objc[29397]: OBJC_PRINT_IVAR_SETUP: log processing of non-fragile ivars
objc[29397]: OBJC_PRINT_IVAR_SETUP is set
objc[29397]: OBJC_PRINT_VTABLE_SETUP: log processing of class vtables
objc[29397]: OBJC_PRINT_VTABLE_SETUP is set
objc[29397]: OBJC_PRINT_VTABLE_IMAGES: print vtable images showing overridden methods
objc[29397]: OBJC_PRINT_VTABLE_IMAGES is set
objc[29397]: OBJC_PRINT_CACHE_SETUP: log processing of method caches
objc[29397]: OBJC_PRINT_CACHE_SETUP is set
objc[29397]: OBJC_PRINT_FUTURE_CLASSES: log use of future classes for toll-free bridging
objc[29397]: OBJC_PRINT_FUTURE_CLASSES is set
objc[29397]: OBJC_PRINT_PREOPTIMIZATION: log preoptimization courtesy of dyld shared cache
objc[29397]: OBJC_PRINT_PREOPTIMIZATION is set
objc[29397]: OBJC_PRINT_CXX_CTORS: log calls to C++ ctors and dtors for instance variables
objc[29397]: OBJC_PRINT_CXX_CTORS is set
objc[29397]: OBJC_PRINT_EXCEPTIONS: log exception handling
objc[29397]: OBJC_PRINT_EXCEPTIONS is set
objc[29397]: OBJC_PRINT_EXCEPTION_THROW: log backtrace of every objc_exception_throw()
objc[29397]: OBJC_PRINT_EXCEPTION_THROW is set
objc[29397]: OBJC_PRINT_ALT_HANDLERS: log processing of exception alt handlers
objc[29397]: OBJC_PRINT_ALT_HANDLERS is set
objc[29397]: OBJC_PRINT_REPLACED_METHODS: log methods replaced by category implementations
objc[29397]: OBJC_PRINT_REPLACED_METHODS is set
objc[29397]: OBJC_PRINT_DEPRECATION_WARNINGS: warn about calls to deprecated runtime functions
objc[29397]: OBJC_PRINT_DEPRECATION_WARNINGS is set
objc[29397]: OBJC_PRINT_POOL_HIGHWATER: log high-water marks for autorelease pools
objc[29397]: OBJC_PRINT_POOL_HIGHWATER is set
objc[29397]: OBJC_PRINT_CUSTOM_CORE: log classes with custom core methods
objc[29397]: OBJC_PRINT_CUSTOM_CORE is set
objc[29397]: OBJC_PRINT_CUSTOM_RR: log classes with custom retain/release methods
objc[29397]: OBJC_PRINT_CUSTOM_RR is set
objc[29397]: OBJC_PRINT_CUSTOM_AWZ: log classes with custom allocWithZone methods
objc[29397]: OBJC_PRINT_CUSTOM_AWZ is set
objc[29397]: OBJC_PRINT_RAW_ISA: log classes that require raw pointer isa fields
objc[29397]: OBJC_PRINT_RAW_ISA is set
objc[29397]: OBJC_DEBUG_UNLOAD: warn about poorly-behaving bundles when unloaded
objc[29397]: OBJC_DEBUG_UNLOAD is set
objc[29397]: OBJC_DEBUG_FRAGILE_SUPERCLASSES: warn about subclasses that may have been broken by subsequent changes to superclasses
objc[29397]: OBJC_DEBUG_FRAGILE_SUPERCLASSES is set
objc[29397]: OBJC_DEBUG_NIL_SYNC: warn about @synchronized(nil), which does no synchronization
objc[29397]: OBJC_DEBUG_NIL_SYNC is set
objc[29397]: OBJC_DEBUG_NONFRAGILE_IVARS: capriciously rearrange non-fragile ivars
objc[29397]: OBJC_DEBUG_NONFRAGILE_IVARS is set
objc[29397]: OBJC_DEBUG_ALT_HANDLERS: record more info about bad alt handler use
objc[29397]: OBJC_DEBUG_ALT_HANDLERS is set
objc[29397]: OBJC_DEBUG_MISSING_POOLS: warn about autorelease with no pool in place, which may be a leak
objc[29397]: OBJC_DEBUG_MISSING_POOLS is set
objc[29397]: OBJC_DEBUG_POOL_ALLOCATION: halt when autorelease pools are popped out of order, and allow heap debuggers to track autorelease pools
objc[29397]: OBJC_DEBUG_POOL_ALLOCATION is set
objc[29397]: OBJC_DEBUG_DUPLICATE_CLASSES: halt when multiple classes with the same name are present
objc[29397]: OBJC_DEBUG_DUPLICATE_CLASSES is set
objc[29397]: OBJC_DEBUG_DONT_CRASH: halt the process by exiting instead of crashing
objc[29397]: OBJC_DEBUG_DONT_CRASH is set
objc[29397]: OBJC_DISABLE_VTABLES: disable vtable dispatch
objc[29397]: OBJC_DISABLE_VTABLES is set
objc[29397]: OBJC_DISABLE_PREOPTIMIZATION: disable preoptimization courtesy of dyld shared cache
objc[29397]: OBJC_DISABLE_PREOPTIMIZATION is set
objc[29397]: OBJC_DISABLE_TAGGED_POINTERS: disable tagged pointer optimization of NSNumber et al.
objc[29397]: OBJC_DISABLE_TAGGED_POINTERS is set
objc[29397]: OBJC_DISABLE_TAG_OBFUSCATION: disable obfuscation of tagged pointers
objc[29397]: OBJC_DISABLE_TAG_OBFUSCATION is set
objc[29397]: OBJC_DISABLE_NONPOINTER_ISA: disable non-pointer isa fields
objc[29397]: OBJC_DISABLE_NONPOINTER_ISA is set
objc[29397]: OBJC_DISABLE_INITIALIZE_FORK_SAFETY: disable safety checks for +initialize after fork
objc[29397]: OBJC_DISABLE_INITIALIZE_FORK_SAFETY is set
Program ended with exit code: 0
  • 也可以通过终端命令export OBJC_HRLP = 1,打印环境变量

  • 这些环境变量,均可以通过target -- Edit Scheme -- Run --Arguments -- Environment Variables配置

2.1.1 环境变量 - OBJC_DISABLE_NONPOINTER_ISA

OBJC_DISABLE_NONPOINTER_ISA=YES ,不使用nonpointerisa:

移除环境变量,使用nonpointerisa:

2.1.2 环境变量 - OBJC_PRINT_LOAD_METHODS

环境变量OBJC_PRINT_LOAD_METHODS=YES,打印重写了load 方法的类

在LGPerson类中重写+load函数(OBJC_PRINT_LOAD_METHODS这个环境可以监听实现了+load的类,做程序启动优化很有帮助,由于+load方法在main函数之前加载,会影响APP启动时间)

2.2 tls_init:线程key的绑定:本地线程池的初始化以及析构

2.3 static_init:运行系统级别的C++静态构造函数

运行库内部的的C++静态函数,在dyld调用我们的静态构造函数之前,libc调用_objc_init方法,即系统级别的C++函数 先自定义的C++函数

2.4 runtime_init:运行时环境初始化

主要是运行时的初始化:

  • 分类初始化
  • 类的表初始化

2.5 exception_init:初始化libobjc的异常处理系统

初始化libobjc的异常处理系统,注册异常处理的回调,监控异常的处理

  • 当crash时,会来到_objc_terminate方法,调用uncaught_handler异常处理函数

  • 可以通过NSSetUncaughtExceptionHandler(&LGExcepptionHandler);给uncaught_handler赋值,捕获应用异常, 如何捕获crash请参考: iOS你不知道的事--Crash分析

2.6 _dyld_objc_notify_register:dyld注册

  • 仅供objc运行时使用

  • 注册处理函数,以便在映射、取消映射和初始化objc镜像时调用

  • dyld将使用包含objc-image-info的镜像文件的数组回调"mapped"函数

  • _dyld_objc_notify_register方法在dyld源码中实现

  • _dyld_objc_notify_register方法的声明

//mapped: dyld将image镜像文件加载进内存时,会触发该函数
//init初始化image会触发该函数
//unmapped: 将image移除时会触发该函数
void _dyld_objc_notify_register(_dyld_objc_notify_mapped    mapped,
                                _dyld_objc_notify_init      init,
                                _dyld_objc_notify_unmapped  unmapped);

image.png

3 dyld与Objc的关联

从上篇的dyld源码可知道

objclib和dyld的关联

&map_images --- sNotifyObjCMapped

load_images --- sNotifyObjCInit

unmap_image --- sNotifyObjCUnmapped

map_images什么时候调用

dyld中全局搜索 sNotifyObjCMapped往上探索调用堆栈,可探索到由dyld::_main发起调用。调用堆栈为:_main --> notifyBatch --> notifyBatchPartial --> sNotifyObjCMapped

  • dyld::_main中调用notifyBatch
gLinkContext.notifyBatch(dyld_image_state_bound, false);
  • notifyBatch中调用notifyBatchPartial
static void notifyBatch(dyld_image_states state, bool preflightOnly)
{
	notifyBatchPartial(state, false, NULL, preflightOnly, false);
}
  • notifyBatchPartial中调用sNotifyObjCMapped

load_image/map_images/unmap_image的调用流程

  • dyld中初始化主程序,进行初始化时,会进入到_objc_init,

  • _objc_init调用_dyld_objc_notify_register给回调函数赋值(sNotityObjcInit= load_image,sNotifyObjCMapped=map_images,sNotifyObjCUnmapped=unmap_image)

  • 然后返回到dyld::_main,在_main中调用sNotityObjcInit,sNotifyObjCMapped,sNotifyObjCUnmapped就可以关联调用objc中的load_image,map_images,unmap_image

4 map_images分析:加载镜像文件

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

关于_read_images详见:iOS- 18.类的加载(1)