类的加载原理(上)

171 阅读2分钟

引入

在之前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_imagesload_imagesunmap_image
  • map_images内容过多我们后面分析,先分析load_images.

load_images

1.png

  • 类的load比分类的load方法先调用,类中load方法调用完才开始调用分类的load方法

  • 分类load方法 谁先编译谁先执行。

  • load_images这里只是当成参数,但在哪里调用我们跟进下。前一篇文章中我们知道,由dyld初始化libsystem->_objc_init->_dyld_objc_notify_register,那么dyld在哪里初始化libsystem了?doModInitFunctions dyld的这个函数,这个函数之后执行了context.notifySingle,在notifSingle里面调用了*sNotifyObjCInit并且把mach-o地址,和mach-o传递进去。等于_dyld_objc_notify_register参数的load_images调用。

environ_init

读取影响运⾏时的环境变量。如果需要,还可以打印环境变量帮助

1.png

环境变量 OBJC_DISABLE_NONPOINTER_ISA

上面的代码只能在objc源码中运行才可以,如果没有objc源码,可以在终端显示 export OBJC_HELP = 1

Xcode中环境变量配置的位置:选中运行的target--> Edit scheme... --> Run --> Arguments --> Environment Variables

1.png

  • 项目跑在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

4.png

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先调用

8.png

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

  • 我们代码简单跑个崩溃代码代码运行调试

1.png

  • 我们可以看到系统把崩溃信息都保存到了uncaught_handler这个函数的参数中。

2.png

  • 我们可以看出系统将崩溃信息通过objc_setUncaughtExceptionHandler的参数fn赋值给uncaught_handler。

  • objc_setUncaughtExceptionHandler它是runtime的实现方式。它在oc层面实现函数是NSSetUncaughtExceptionHandler,我们可以在oc层实现这个函数,在写个方法将exception接收过来就能实现crash收集工作。

4.png

  • 只需要注册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

9.png

  • 它在notifyBatchPartial中有调用。
  • notifyBatchPartialregisterObjCNotifiers中有调用。

11.png

  • 所以是先执行&map_images,然后在执行load_images