dyld与objc的关联

1,589 阅读3分钟
  • _objc_init 源码解析

    先看源码主要是一些列东西的初始化:
    1. environ_init
      读取影响运⾏时的环境变量。如果需要,还可以打印环境变量帮助。export OBJC_HELP = 1 源码: 冲源码中发现可以通过配置OBJC_HELPOBJC_PRINT_OPTIONS两个环境变量来打印所有环境变量。配置环境变量的方法target -- Edit Scheme -- Run --Arguments -- Environment Variables
      第二种打印环境变量的方式是通过终端指令export OBJC_hrlp = 1
    2. tls_init
      关于线程 key 的绑定,比如每线程数据的析构函数。
    3. static_init
      这里会运行 C++ 的静态构造函数,在 dyld 调用我们的静态构造函数之前,libc 会调用 _objc_init,所以这里我们必须自己来做,并且这里只会初始化系统内置的 C++ 静态构造函数,我们自己代码里面写的并不会在这里初始化。所以系统的c++函数会先于自定义的c++函数执行
    4. runtime_init
      主要是运行时的初始化,主要分为两部分:分类初始化、类的表初始化
    5. exception_init
      初始化 libobjc 的异常处理系统 发现会走到_objc_terminate方法中去 再看uncaught_handler方法发现默认是个空实现在全局搜索uncaught_handler发现调用objc_setUncaughtExceptionHandler可以设置uncaught_handler方法所以应用记得crash的拦截可以通过设置uncaught_handler方法拦截也就是调用objc_setUncaughtExceptionHandler方法
    6. 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
      }
      
    7. _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
      }
      
    8. _dyld_objc_notify_register
      dyld注册,具体源码在dyld源码中
      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
          }
      
          // <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());
              }
          }
      }
      
  • dyld与objc的关联

    上述源码中发现objc初始化的方法中会注册dyld,会传入
    map_imagesdyld将image(镜像文件)加载进内存时,会触发该函数
    load_imagesdyld初始化image会触发该函数
    unmap_imagedyld将image移除时,会触发该函数
    而在dyld源码中在registerObjCNotifiers方法源码中发现dyld分别将三个方法赋值给 sNotifyObjCMapped = mapped; sNotifyObjCInit = init; sNotifyObjCUnmapped = unmapped;相当于
    sNotifyObjCMapped = mapped = map_images
    sNotifyObjCInit = init = load_images
    sNotifyObjCUnmapped = unmapped = unmap_image
    • load_images方法的出发时机 上文中分析dyld的加载流程juejin.cn/post/691870…的时候知道load_images方法调用在dyld中其实就是sNotifyObjCInit调用sNotifyObjCInit的调用又在notifySingle函数中执行notifySingle函数的调用是在dyld::main函数中初始化镜像文件的时候调用
    • map_images方法的出发时机 上文分析中得知map_images函数的调用在dyld中对应的就是sNotifyObjCMapped的调用,所以在dyld源码中全局搜索sNotifyObjCMapped发现是在notifyBatchPartial函数中被调用然后再全局搜索notifyBatchPartial调用的地方发现是在registerObjCNotifiers注册通知的时候调用的,所以冲这里也可以知道map_images函数其实在_objc_init函数中被调用,同时也验证了map_images的执行是在load_images函数之前的 最后总结dyld与objc的关联如下:
      在dyld中注册回调函数,可以理解为 添加观察者
      在objc中dyld注册,可以理解为发送通知
      触发回调,可以理解为执行通知selector