zoukankan      html  css  js  c++  java
  • iOS 底层原理之—dyld 与 objc 的关联

    前言

     

    dyld加载过程中,我们知道会调用_objc_init方法,那么在_objc_init方法中究竟做了什么呢?我们来探究下。

     

    _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();
    cache_init();
    _imp_implementationWithBlock_init();
     
    _dyld_objc_notify_register(&map_images, load_images, unmap_image);
     
    #if __OBJC2__
    didCallDyldNotifyRegister = true;
    #endif
    }
     
     
     
     

    _objc_init实现中我们分析下该方法主要做了什么

     

    environ_init()

     

    该方法主要是读取运行时的环境变量,我们可以通过设置DYLD_PRINT_STATISTICS = YES来打印APP启动到main()函数之前的时长,进而可以进行APP启动优化。具体的environ_init()简介可参考博客iOS-底层原理 16:dyld与objc的关联中有关nviron_init()部分的介绍

     

    tls_init()

     

    主要用于关于线程key的绑定,比如每线程数据的析构函数。

     

     
     
     
    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
    }
     
     
     
     

    static_init()

     

    主要是C++静态构造函数

     

     
     
     
    static void static_init()
    {
    size_t count;
    auto inits = getLibobjcInitializers(&_mh_dylib_header, &count);
    for (size_t i = 0; i < count; i++) {
    inits[i]();
    }
    }
     
     
     
     

    runtime_init()

     

    主要是运行时的初始化,主要分为两部分:分类初始化类的表初始化

     

     
     
     
    void runtime_init(void)
    {
    objc::unattachedCategories.init(32);
    objc::allocatedClasses.init();
    }
    复制代码
     
     
     
     

    exception_init()

     

    初始化libobjc异常处理

     

     
     
     
    /***********************************************************************
    * exception_init
    * Initialize libobjc's exception handling system.
    * Called by map_images().
    **********************************************************************/
    void exception_init(void)
    {
    old_terminate = std::set_terminate(&_objc_terminate);
    }
     
     
     
     

    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
    }
     
     
     
     

    _imp_implementationWithBlock_init()

     

    主要用来启动机制回调

     

     
     
     
    /// 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
    }
     
     
     
     

    dyld与objc关联

     

    _dyld_objc_notify_register(&map_images, load_images, unmap_image)

     

    主要是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);
    }
     
     
     
     

    从上文正中我们可以看出

     

    • mappedmap_images

    • initload_images

    • unmappedunmap_image

     

    map_images()函数分析

     

     
     
     
    /***********************************************************************
    * map_images
    * Process the given images which are being mapped in by dyld.
    * Calls ABI-agnostic code after taking ABI-specific locks.
    *
    * Locking: write-locks runtimeLock
    **********************************************************************/
    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函数中我们发现map_images_nolock函数是重点,我们进入map_images_nolock函数

     

    map_images_nolock

     

    我们查看代码实现

     

    从截图中我们可以看出_read_images是我们要重点研究的方法

     

    _read_images函数分析

     

    是否是第一次加载

     

    修复预编译时@selector的错乱问题

     

    错误类处理,通过readClass读取出来类的信息

     

    重新设置映射镜像

     

    消息处理

     

    类中如果有协议,读取协议

     

    映射协议

     

    加载分类

     

    注意在分类处理中主要是通过load_categories_nolock处理,我们进入load_categories_nolock函数中

     

    load_categories_nolock函数

     

     
     
     
    static void load_categories_nolock(header_info *hi) {
    bool hasClassProperties = hi->info()->hasCategoryClassProperties();
     
    size_t count;
    auto processCatlist = [&](category_t * const *catlist) {
    for (unsigned i = 0; i < count; i++) {
    category_t *cat = catlist[i];
    Class cls = remapClass(cat->cls);
    locstamped_category_t lc{cat, hi};
     
    if (!cls) {
    // Category's target class is missing (probably weak-linked).
    // Ignore the category.
    if (PrintConnecting) {
    _objc_inform("CLASS: IGNORING category ???(%s) %p with "
    "missing weak-linked target class",
    cat->name, cat);
    }
    continue;
    }
     
    // Process this category.
    if (cls->isStubClass()) {
    // Stub classes are never realized. Stub classes
    // don't know their metaclass until they're
    // initialized, so we have to add categories with
    // class methods or properties to the stub itself.
    // methodizeClass() will find them and add them to
    // the metaclass as appropriate.
    if (cat->instanceMethods ||
    cat->protocols ||
    cat->instanceProperties ||
    cat->classMethods ||
    cat->protocols ||
    (hasClassProperties && cat->_classProperties))
    {
    objc::unattachedCategories.addForClass(lc, cls);
    }
    } else {
    // First, register the category with its target class.
    // Then, rebuild the class's method lists (etc) if
    // the class is realized.
    if (cat->instanceMethods || cat->protocols
    || cat->instanceProperties)
    {
    if (cls->isRealized()) {
    attachCategories(cls, &lc, 1, ATTACH_EXISTING);
    } else {
    objc::unattachedCategories.addForClass(lc, cls);
    }
    }
     
    if (cat->classMethods || cat->protocols
    || (hasClassProperties && cat->_classProperties))
    {
    if (cls->ISA()->isRealized()) {
    attachCategories(cls->ISA(), &lc, 1, ATTACH_EXISTING | ATTACH_METACLASS);
    } else {
    objc::unattachedCategories.addForClass(lc, cls->ISA());
    }
    }
    }
    }
    };
     
    processCatlist(_getObjc2CategoryList(hi, &count));
    processCatlist(_getObjc2CategoryList2(hi, &count));
    }
     
     
     
     

    load_categories_nolock函数实现中,我们可以看到该函数将实例方法协议属性类方法等再次链接了一次。

     

    非懒加载类处理

     

    处理没有使用的类

     

    dyld与objc关联总结

     

    • dyld_start调用_objc_init来初始化,_objc_init中通过dyld调用_dyld_objc_notify_register函数,传入map_imagesload_images这两个参数来处理

    • map_images通过map_images_nolock函数调用_read_images函数

    • _read_images函数中处理类信息、属性、协议、分类等

    • 当一切准备妥当,则再次返回dyld_start中,此时dyldobjc关联了起来

     

    资料推荐

     

    如果你正在跳槽或者正准备跳槽不妨动动小手,添加一下咱们的交流群1012951431来获取一份详细的大厂面试资料为你的跳槽多添一份保障。

     

  • 相关阅读:
    html集合
    pyautocad
    CAD 批量提取点坐标,实现坐标的快速提取
    CAD
    python模块
    set,get,setter
    1 Http的表皮
    (6)小项目------完善增删改查的操作
    SSM学习笔记(6)---拦截器
    SSM学习笔记(5)-CGLIB动态代理
  • 原文地址:https://www.cnblogs.com/Julday/p/13845355.html
Copyright © 2011-2022 走看看