美文网首页
dyld和ObjC的关联

dyld和ObjC的关联

作者: 奉灬孝 | 来源:发表于2020-10-13 23:00 被阅读0次

    iOS dyld加载流程里我们讲述了 dyld 的加载流程, 那么是如何与ObjC关联起来的呢?

    • main 函数执行过程中,当 dyld 加载到开始链接主程序的时候 , 递归调用 recursiveInitialization 函数。
    • recursiveInitialization 函数第一次执行 , 进行 libsystem 的初始化 。 其执行过程为:recursiveInitialization -> doInitialization -> doModInitFunctions -> libSystemInitialized
    • libsystem 的初始化 , 它会调用起 libdispatch_init , libdispatchinit 会调用 _os_object_init , 这个函数里面调用了 _objc_init
    • _objc_init 中注册并保存了 map_images , load_images , unmap_image 函数地址,从而进入了我们类的加载过程。
    objc4-781官方源码_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();
    
        // 什么时候调用? images 镜像文件
        // map_images()
        // load_images()
        
        _dyld_objc_notify_register(&map_images, load_images, unmap_image);
    
    #if __OBJC2__
        didCallDyldNotifyRegister = true;
    #endif
    }
    

    1. 环境变量初始化(environ_init)

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

    我们可以点击进入环境变量初始化 environ_init 方法,将控制环境变量打印信息的条件都屏蔽掉,代码如下所示:

    for (size_t i = 0; i < sizeof(Settings)/sizeof(Settings[0]); i++) {
            const option_t *opt = &Settings[i];
            _objc_inform("%s: %s", opt->env, opt->help);
            _objc_inform("%s is set", opt->env);
        }
    

    控制台打印日志如下:

    ···
    objc[38640]: OBJC_DISABLE_TAG_OBFUSCATION is set
    objc[38640]: OBJC_DISABLE_NONPOINTER_ISA: disable non-pointer isa fields
    objc[38640]: OBJC_DISABLE_NONPOINTER_ISA is set
    objc[38640]: OBJC_DISABLE_INITIALIZE_FORK_SAFETY: disable safety checks for +initialize after fork
    objc[38640]: OBJC_DISABLE_INITIALIZE_FORK_SAFETY is set
    

    在不设置环境变量 OBJC_DISABLE_NONPOINTER_ISA 的时候,打印 personisa 信息

    lldb) x/4gx person
    0x1010b5680: 0x001d800100008265 0x0000000000000000
    0x1010b5690: 0x0000000000000000 0x0000000000000000
    (lldb) p/t 0x001d800100008265
    (long) $1 = 0b0000000000011101100000000000000100000000000000001000001001100101
    

    然后设置环境变量 OBJC_DISABLE_NONPOINTER_ISAYES,之后再次打印 personisa 信息

    设置环境变量OBJC_DISABLE_NONPOINTER_ISA 为 YES
    (lldb) x/4gx person
    0x100a09f20: 0x0000000100008260 0x0000000000000000
    0x100a09f30: 0x0000000000000000 0x0000000000000000
    (lldb) p/t 0x0000000100008260
    (long) $1 = 0b0000000000000000000000000000000100000000000000001000001001100000
    

    这里我们可以看到最后一位的变换:由 1 变为 0,在isa底层结构分析中我们介绍过,最后一位就是 nonpointer 位,表示是否对 isa 指针开启指针优化。 0:纯 isa 指针;1:不止是类对象地址。isa 中包含了类信息、对象的引用计数等。

    我们设置打印所有加载的文件的相关的load方法,设置环境 OBJC_PRINT_LOAD_METHODS = YES然后再次打印,控制台日志如下:

    ···
    objc[39088]: LOAD: class 'NSApplication' scheduled for +load
    objc[39088]: LOAD: class 'NSBinder' scheduled for +load
    objc[39088]: LOAD: class 'NSColorSpaceColor' scheduled for +load
    objc[39088]: LOAD: class 'NSNextStepFrame' scheduled for +load
    objc[39088]: LOAD: category 'NSColor(NSUIKitSupport)' scheduled for +load
    objc[39088]: LOAD: +[NSApplication load]
    ···
    

    2. 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
    }
    

    3. static_init

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

    4. runtime运行时环境初始化(runtime_init)

    void runtime_init(void)
    {
        objc::unattachedCategories.init(32);  // 分类的初始化
        objc::allocatedClasses.init(); // 储存加载完毕的类的方法
    }
    

    5. 异常信息的初始化(exception_init)

    • 我们可以通过设置回调函数来拦截异常信息

    6.缓存条件初始化 (cache_init)

    7. 启动回调机制(_imp_implementationWithBlock_init)

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

    8. _dyld_objc_notify_register

    _dyld_objc_notify_register 这个方法是跨库执行的,在苹果开源的 dyld 源码里面可以找到,然后看到调用了 dyld::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());
            }
        }
    }
    
    8.1 load_images

    接下来我们分析一下 registerObjCNotifiers 方法的第二个参数 init,(也就是_dyld_objc_notify_register 方法中的 load_images 参数)方法里面有sNotifyObjCInit = init; 这个赋值语句,接下来我们在 dyld 源码中全局搜索一下 sNotifyObjCInit ,会发现在 notifySingle 方法中有 sNotifyObjCInit 的调用:

    (*sNotifyObjCInit)(image->getRealPath(), image->machHeader());
    

    所以在 ObjC_objc_init 方法里调用的 _dyld_objc_notify_register 方法终于在 dyld 源码中找到其真正调用的地方,终于找到了它们真正的关联关系了。

    8.2 map_images

    那么 _dyld_objc_notify_register 方法中的 map_images 参数呢?接下来我们继续找 registerObjCNotifiers 方法中的 sNotifyObjCMapped = mapped; 这一赋值语句,在 dyld 源码中全局搜索一下 sNotifyObjCMapped ,同样会发现在 notifyBatchPartial 方法中有 sNotifyObjCMapped 的调用:

    (*sNotifyObjCMapped)(objcImageCount, paths, mhs);
    

    所以说 ObjC_objc_init 方法的调用时离不开 dyld 的,它们之间有着紧密的联系。

    我们还可以继续探索一下 map_images, 进入到 objc_781 源码,全局搜索相关的函数调用 map_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 方法中,我们能发现其中有很多加载相关类的信息 _read_images

    if (hCount > 0) {
        _read_images(hList, hCount, totalClasses, unoptimizedTotalClasses);
    }
    

    我们知道,map_images 这个函数的主要功能就是为了映射相关的类信息,所以此处才是我们研究的重点,接着进入到相关的类方法的定义 _read_images 方法,顺着源码分析我们能得到很多关于类的信息:

    1. 条件控制进⾏⼀次的加载
    2. 修复预编译阶段的 @selector 的混乱问题
    3. 错误混乱的类处理
    4. 修复重映射⼀些没有被镜像⽂件加载进来的 类
    5. 修复⼀些消息!
    6. 当我们类⾥⾯有协议的时候 : readProtocol
    7. 修复没有被加载的协议
    8. 分类处理
    9. 类的加载处理
    10. 没有被处理的类 优化那些被侵犯的类

    _read_images 方法中同样会执行到 readClass 方法,我们在 readClass 方法方法执行前后各分别设置断点来查看一下这个方法到底做了什么,发现该方法是对类进行名字赋值。

    readClass方法执行前后
    Class readClass(Class cls, bool headerIsBundle, bool headerIsPreoptimized)
    {
        const char *mangledName = cls->mangledName();
        
        if (missingWeakSuperclass(cls)) {...}
        
        cls->fixupBackwardDeployingStableSwift();
    
        Class replacing = nil;
        if (Class newCls = popFutureNamedClass(mangledName)) {...}
        
        if (headerIsPreoptimized  &&  !replacing) {...} else {
        // 添加类名,
            addNamedClass(cls, mangledName, replacing);
            addClassTableEntry(cls);
        }
    
        // for future reference: shared cache never contains MH_BUNDLEs
        if (headerIsBundle) {
            cls->data()->flags |= RO_FROM_BUNDLE;
            cls->ISA()->data()->flags |= RO_FROM_BUNDLE;
        }
        
        return cls;
    }
    

    查看添加类名 addNamedClass 方法源码 如下:

    该方法为将类地址与名字进行映射,并插入内存。

    static void addNamedClass(Class cls, const char *name, Class replacing = nil)
    {
        runtimeLock.assertLocked();
        Class old;
        if ((old = getClassExceptSomeSwift(name))  &&  old != replacing) {
            inform_duplicate(name, old, cls);
    
            // getMaybeUnrealizedNonMetaClass uses name lookups.
            // Classes not found by name lookup must be in the
            // secondary meta->nonmeta table.
            addNonMetaClass(cls);
        } else {
            NXMapInsert(gdb_objc_realized_classes, name, cls);
        }
        ASSERT(!(cls->data()->flags & RO_META));
    
        // wrong: constructed classes are already realized when they get here
        // ASSERT(!cls->isRealized());
    }
    

    查看 mangledName 方法源码,如下:

    该方法为读取类的名字

    const char *mangledName() { 
        // fixme can't assert locks here
        ASSERT(this);
    
        if (isRealized()  ||  isFuture()) {
            return data()->ro()->name;
        } else {
            return ((const class_ro_t *)data())->name;
        }
    }
    

    _read_images 方法中有非懒加载: Realize non-lazy classes (for +load methods and static instances) 代码如下:

    注释中给出了将类变为非懒加载类的方式,即添加+load方法。

    // Realize non-lazy classes (for +load methods and static instances)
    for (EACH_HEADER) {
        classref_t const *classlist = 
            _getObjc2NonlazyClassList(hi, &count);
        for (i = 0; i < count; i++) {
            Class cls = remapClass(classlist[i]);
            
            const char *mangledName  = cls->mangledName();
            const char *LGPersonName = "LGPerson";
           
            if (strcmp(mangledName, LGPersonName) == 0) {
                auto kc_ro = (const class_ro_t *)cls->data();
                printf("_getObjc2NonlazyClassList: 这个是我要研究的 %s \n",LGPersonName);
            }
            
            if (!cls) continue;
    
            addClassTableEntry(cls);
    
            if (cls->isSwiftStable()) {
                if (cls->swiftMetadataInitializer()) {
                    _objc_fatal("Swift class %s with a metadata initializer "
                                "is not allowed to be non-lazy",
                                cls->nameForLogging());
                }
                // fixme also disallow relocatable classes
                // We can't disallow all Swift classes because of
                // classes like Swift.__EmptyArrayStorage
            }
            realizeClassWithoutSwift(cls, nil);
        }
    }
    

    添加 +load 方法进行执行
    在此 + load 方法中我们用断点调试相关的程序,我们知道系统类的实现对我们来说是不可见的,所所以我们研究系统类的实现以及加载难度太大以及成本太高并且是得不偿失。所以我们研究自己定义的类最好不过,我们在自己实现的类中实现 +load 方法,

    + (void)load{
       NSLog(@"%s",__func__);
    }
    

    添加了 +load 方法后, 发现会运行到 realizeClassWithoutSwift 方法内,查看该方法内容 realizeClassWithoutSwift 方法源码,源码如下

    • 再次通过 mangledName 筛选出我们所定义的类的实现,定位到当前类
    • 存在 data() 获取,将 ro 的内容复制给 rw : rw->set_ro(ro);(rw表示readWrite,由于动态性,可能会往类中添加属性、方法、添加协议。ro表示readOnly,在编译时已经确定了内存。)
    • 最后执行类的加载执行方法 methodizeClass
    static Class realizeClassWithoutSwift(Class cls, Class previously)
    {
        runtimeLock.assertLocked();
    
        class_rw_t *rw;
        Class supercls;
        Class metacls;
        
        // 自己写的,目的是只研究自己创建的类
        // 自己写的 start
        const char *mangledName  = cls->mangledName();
        const char *LGPersonName = "LGPerson";
        if (strcmp(mangledName, LGPersonName) == 0) {
            auto kc_ro = (const class_ro_t *)cls->data();
            auto kc_isMeta = kc_ro->flags & RO_META;
            if (!kc_isMeta) {
                printf("%s: 这个是我要研究的 %s \n",__func__,LGPersonName);
            }
        }
        
        if (!cls) return nil;
        if (cls->isRealized()) return cls;
        ASSERT(cls == remapClass(cls));
    
        // fixme verify class is not in an un-dlopened part of the shared cache?
        // data() -> ro 获取ro
        auto ro = (const class_ro_t *)cls->data();
        auto isMeta = ro->flags & RO_META;
        if (ro->flags & RO_FUTURE) {...} else {
            // Normal class. Allocate writeable class data.
            // 创建rw空间
            rw = objc::zalloc<class_rw_t>();
            // 将ro的内容复制给rw
            rw->set_ro(ro);
            rw->flags = RW_REALIZED|RW_REALIZING|isMeta;
            // 当前类引用rw
            cls->setData(rw);
        }
        省略...
    
        // Propagate the associated objects forbidden flag from ro or from
        // the superclass.
        if ((ro->flags & RO_FORBIDS_ASSOCIATED_OBJECTS) ||
            (supercls && supercls->forbidsAssociatedObjects()))
        {
            rw->flags |= RW_FORBIDS_ASSOCIATED_OBJECTS;
        }
    
        // Connect this class to its superclass's subclass lists
        if (supercls) {
            addSubclass(supercls, cls);
        } else {
            addRootClass(cls);
        }
    
        // Attach categories
        methodizeClass(cls, previously);
    
        return cls;
    }
    

    所以非懒加载类的执行流程为:

    • _getObjc2NonlazyClassList
    • readClass
    • realizeClassWithoutSwift
    • methodizeClass

    对于懒加载类:数据加载推迟到第一次消息即第一次调用方法的时候,执行方法的流程为:

    懒加载执行流程.png
    • lookUpImpOrForward
    • realizeClassMaybeSwiftMaybeRelock
    • realizeClassWithoutSwift
    • methodizeClass
    懒加载和非懒加载类执行流程.png
    rw 与 ro 的关系
    • rwread write , roread onlyOC 为了动态的特性 , 在编译器确定并保存了一份 类的结构数据在 ro 中 , 另外存储一份在运行时加载到 rw 中 , 供 runtime 动态修改使用 。
    • ro 是不可变的 , 而 rwmethods , properties 以及 protocols 内存空间是可变的 。这也是已有类为什么可以动态添加方法 , 却不能动态添加属性的原因。 ( 添加属性会同样添加成员变量 , 也就是 ivar . 而 ivar是存储在 ro 中的 )
    • 同样分类不能添加属性的原因也是如此。 ( 关联属性是单独存储在 ObjectAssociationMap 中的 , 跟类的原理并不一样 )

    相关文章

      网友评论

          本文标题:dyld和ObjC的关联

          本文链接:https://www.haomeiwen.com/subject/qwqbpktx.html