OC底层原理探索之类的加载原理上

220 阅读4分钟

_objc_init入口

通过上篇文章的介绍我们知道dyld记载的images只是库,那么我们的库是如何加载到内存中去的呢,也就是如何把mach-o文件地址读取出来存到相应的内存中。 images(mach-o)->地址->表->类->初始化(ro rw)

void _objc_init(void)
{
    // 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);
    // map_images()
    // load_images()
#if __OBJC2__
    didCallDyldNotifyRegister = true;
#endif
}

environ_init()

environ_init()读取影响运行时的环境变量。可以在里面打印环境变量帮助。该函数中把这个打印前置,可以看到控制台打印帮助 image.png **objc[12334]: OBJC_DISABLE_NONPOINTER_ISA: disable non-pointer isa fields**我们知道默认创建的类都是**NONPOINTER_ISA**,那么如何创建一个纯的**POINTER_ISA** image.png 默认创建的isa二进制末尾是1,我们在Edit scheme中设置 image.png 再运行打印p的isa image.png 我们发现设置之后打印的就是末尾为0的ISA也就是POINTER_ISA,还可以打印load方法等等

tls_init()

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

static_init()

运行c++静态构造函数。Libc在dyld调用静态构造函数之前调用_objc_init()。这里的c++调用时机比dyld的早,只找自身的

runtime_init()

void runtime_init(void)
{
    objc::unattachedCategories.init(32);
    objc::allocatedClasses.init();
}

runtime运行时环境初始化,里面投两张表的初始化

exception_init()

初始化libobjc的异常处理系统,底层拦截异常的是objc_setUncaughtExceptionHandler函数,对应的上层OC的函数是NSSetUncaughtExceptionHandler(&ExceptionHandlers),我们可以在方法ExceptionHandlers中处理上报或者拦截异常。

objc_uncaught_exception_handler 
objc_setUncaughtExceptionHandler(objc_uncaught_exception_handler fn)
{
    objc_uncaught_exception_handler result = uncaught_handler;
    uncaught_handler = fn;
    return result;
}

cache_t::init()

缓存条件初始化

_imp_implementationWithBlock_init()

启动回调机制,通常这不会做什么,因为所有的初始化都是惰性的,但是对于某一些进程会先加载trampolines dylib

_dyld_objc_notify_register(&map_images, load_images, unmap_image)

&map_images指针传递,这里是映射镜像文件,只要一处改动,全局都随着变动。 load_images值拷贝,这里面主要是load方法的调用。

map_images

管理文件中和动态库中所有的符号 map_images -> map_images_nolock - > map_images_nolock 发现了镜像文件的读取过程。

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

_read_images里面可以看到很多log

ts.log("IMAGE TIMES: first time tasks"); 
ts.log("IMAGE TIMES: fix up selector references");
ts.log("IMAGE TIMES: discover classes");
ts.log("IMAGE TIMES: remap classes");
ts.log("IMAGE TIMES: fix up objc_msgSend_fixup");
ts.log("IMAGE TIMES: discover protocols");
ts.log("IMAGE TIMES: fix up @protocol references");
ts.log("IMAGE TIMES: discover categories");
ts.log("IMAGE TIMES: realize non-lazy classes");
ts.log("IMAGE TIMES: realize future classes");

1.条件控制进行第一次的加载 2.修复预编译阶段的selector 的引用 3.错误混乱的类处理 future class 4.修复重新映射一些没有被镜像文件加载进来的类 5.修复一些objc_msgSend 6.当我们的类里面有协议的时候 readProtocol 7.修复没有被加载的协议 8.分类的处理 9.类的加载处理 10.没有被处理的类,feature class也就是野指针 ​

我们按照log块来分析 log1: 看到了小对象的加载,这里的作用是混淆objc_debug_taggedpointer_obfuscator &= ~_OBJC_TAG_MASK

    initializeTaggedPointerObfuscator(); // 小对象加载
        // namedClasses
        // Preoptimized classes don't go in this table.
        // 4/3 is NXMapTable's load factor
        // objc::allocatedClasses.init(); 
        // 预优化的类不在这个表中,4/3是NXMapTable的负载因子,
     int namedClassesSize = 
            (isPreoptimized() ? unoptimizedTotalClasses : totalClasses) * 4 / 3;
     gdb_objc_realized_classes =
            NXCreateMapTable(NXStrValueMapPrototype, namedClassesSize);

     ts.log("IMAGE TIMES: first time tasks");

gdb_objc_realized_classes创建了一个表,这个表实际上是一个命名类的列表,不在dyld共享缓存中,无论是否实现,而runtime_initobjc::allocatedClasses.init()是已经开辟过内存的表,这两个表是不同的。 log2:在这里对两个表中同名不同地址的sel做修正操作。_getObjc2SelectorRefs是通过mach-o读取出来的,mach-o有相对位移地址和偏移地址;而sel_registerNameNoLock是从dyld加载出来的,由于mach-o这个文件随时会改变的,所以以dyld里面的内容为准,所以sels[i] = sel


   // sel 名字 + 地址
    static size_t UnfixedSelectors;
    {
        mutex_locker_t lock(selLock);
        for (EACH_HEADER) {
            if (hi->hasPreoptimizedSelectors()) continue;

            bool isBundle = hi->isBundle();
            SEL *sels = _getObjc2SelectorRefs(hi, &count);// 从mach-o读出
            UnfixedSelectors += count;
            for (i = 0; i < count; i++) {
                const char *name = sel_cname(sels[i]);
                SEL sel = sel_registerNameNoLock(name, isBundle); // 从dyld加载出来
                if (sels[i] != sel) {
                    sels[i] = sel;
                }
            }
        }
    }

    ts.log("IMAGE TIMES: fix up selector references");

log3:3644行的时候,cls还是一个从mach-o读取出来的地址,到了3646行,变成了一个__NSStackBlock__意味着对类进行了一些处理 image.png

readClass

那么这里对类进行了哪些处理,我们写个测试代码,

Class readClass(Class cls, bool headerIsBundle, bool headerIsPreoptimized)
{
    const char *mangledName = cls->nonlazyMangledName();
    // 测试代码
    const char *PersonName = "Person";
    if (strcmp(mangledName, PersonName) == 0) {
        printf("%s - %s\n",__func__,mangledName);
    }
    ...
}

如果if==0则表明到了我们的Person类,断点卡在这里看下一步到了哪里。下面的代码很熟悉,就是类的ro rw等的处理,第一感觉是会走到这里,真的是这样的吗?

	    class_rw_t *rw = newCls->data();
            const class_ro_t *old_ro = rw->ro();
            memcpy(newCls, cls, sizeof(objc_class));

            // Manually set address-discriminated ptrauthed fields
            // so that newCls gets the correct signatures.
            newCls->setSuperclass(cls->getSuperclass());
            newCls->initIsa(cls->getIsa());

            rw->set_ro((class_ro_t *)newCls->data());
            newCls->setData(rw);
            freeIfMutable((char *)old_ro->getName());
            free((void *)old_ro);

经过实验,我们发现Person并没有走到这里。所以类的ro rw 分类和协议等不是在这里处理的。 那么我们是在哪里加载类的信息。 log9:类的加载处理,既然上面没有,那么很自然而言的就想到了是在这里面实现的realizeClassWithoutSwift(cls, nil);