第十五节—libobjc探索类的加载(一)

本文为L_Ares个人写作,以任何形式转载请表明原文出处。

dyld已经进入到了libobjc库,毕竟dyld只是动态链接器,它要完成的是把不能自己加载到内存的动态库和我们的程序代码进行链接(link),然后把它们变成的可执行文件mach-o加载到内存中执行。

也就是说,mach-o有需要动态库,需要和动态库链接,用它的东西,才需要dyld帮助链接,并帮他们把链接后的内容加载到内存执行。所以还需要知道到动态库中有哪些东西是经常使用,需要经常被dyld把它们链接到我们的代码中的。

那么就从类的加载开始,因为万物皆对象吗,都是类实例化的。

上一节已经知道了dyld把镜像都搞定了,libobjc要开始映射镜像,并且加载镜像,那么本节就从如何读取镜像中的数据,如何再把数据放入内存中执行入手。

探索准备 : dyld源码750.6,请自行下载。objc4-781。请看教程,里面有配置好的文件

一、read_images

进入libobjc的初始化入口_objc_init()。前面各种环境和内容的初始化上节都详细说了,本节直接从最后一行_dyld_objc_notify_register(&map_images, load_images, unmap_image);开始看libobjc怎么从镜像中拿到数据的。

进入map_images看映射镜像的实现。

//处理dyld映射的镜像
void
map_images(unsigned count, const char * const paths[],
           const struct mach_header * const mhdrs[])
{
    //一个写入的锁
    mutex_locker_t lock(runtimeLock);
    //这里才是处理dyld映射的镜像
    return map_images_nolock(count, paths, mhdrs);
}
  • 一个写入的锁。
  • 返回dyld映射的镜像的处理

进入map_images_nolock。前面全都是对hCount做操作,做判断,也就是说hCount极有可能就是要加载的镜像的数量。这些都先不管,直接找到读取镜像的地方。

图1.0.png

那么进入_read_images看它是做什么的。因为代码很多,就不贴出来了,思路是把里面的iffor的代码都折叠,然后就会发现这是一段非常整齐的代码段,描述了_read_images的流程。我就直接说流程。

_read_images的流程

  1. 判断是否第一次进入_read_images函数。
    if (!doneOnce)
  2. 修复预编译阶段与@selector引用的问题。
    static size_t UnfixedSelectors;
  3. 发现类,修复未解决的问题。
    bool hasDyldRoots
  4. 修复重新映射的类。
    if (!noClassesRemapped())
  5. 修复旧的objc_msgSend_fixup调用站点。
    fixupMessageRef
  6. 发现协议。修复协议引用。
    readProtocol
  7. 修复@protocol的引用。
    remapProtocolRef
  8. 分类的处理。
    if (didInitialAttachCategories)
  9. 实现非懒加载的类。
    realizeClassWithoutSwift
  10. 实现一些新解析的类,防止CoreFoundation操作了它们。
    if (resolvedFutureClasses)

下面分别的来看一些重点的流程。

1. 第一次进入_read_images

找到这里。

        //无论是没有预先优化的类,还是类的总数量,namedClassesSize的大小都是它们的大小 * 4 / 3
        int namedClassesSize = 
            (isPreoptimized() ? unoptimizedTotalClasses : totalClasses) * 4 / 3;
        //内存开辟一个Maptable,大小就是namedClassesSize的大小
        gdb_objc_realized_classes =
            NXCreateMapTable(NXStrValueMapPrototype, namedClassesSize);

        ts.log("IMAGE TIMES: first time tasks");
  • gdb_objc_realized_classes : 有官方的注释。解释一下,就是说只要不是dyld的共享缓存里面的类,无论是否进行了实现,都放在这张表里面。也就是说这是一张表,存放着所有不在dyld共享缓存中的类。

  • namedClassesSize : gdb_objc_realized_classes这个表的大小。

    • 如果有预先的优化,大小就是未经过优化的类的总数的4倍再除以3取整。
    • 如果没有预先优化,就是类的总数的4倍除以3取整。

2. 修复@selector的引用

//修复sel的引用
    static size_t UnfixedSelectors;
    {
        mutex_locker_t lock(selLock);
        for (EACH_HEADER) {
            //hi是header_info *hi,上面定义过,也就是头文件信息
            if (hi->hasPreoptimizedSelectors()) continue;

            //判断头文件信息在不在路径里面
            bool isBundle = hi->isBundle();
            
            //拿到Mach-O中的静态段__objc_selrefs
            SEL *sels = _getObjc2SelectorRefs(hi, &count);
            UnfixedSelectors += count;
            for (i = 0; i < count; i++) {
                const char *name = sel_cname(sels[i]);
                //注册sel的name
                SEL sel = sel_registerNameNoLock(name, isBundle);
                //如果发现对应位置的sel和注册name的sel地址不一样,那么就把注册名字的sel的地址赋值进去
                if (sels[i] != sel) {
                    sels[i] = sel;
                }
            }
        }
    }
  • 就是利用_getObjc2SelectorRefs拿到mach-o中的_objc_selrefs
  • 把数量也拿到,看看多少个需要修复。
  • 然后把这些需要修复的selrefs遍历,拿到name
  • 利用sel_registerNameNoLock进行selname的注册。
  • 如果_objc_selref和经过name注册的sel地址不一样,那么就把_objc_selref替换成已经注册了namesel
2.1 _getObjc2SelectorRefs
图1.2.1.png
2.2 sel_registerNameNoLock
SEL sel_registerNameNoLock(const char *name, bool copy) {
    return __sel_registerName(name, 0, copy);  // NO lock, maybe copy
}

接着进。

static SEL __sel_registerName(const char *name, bool shouldLock, bool copy) 
{
    SEL result = 0;

    if (shouldLock) selLock.assertUnlocked();
    else selLock.assertLocked();

    if (!name) return (SEL)0;

    result = search_builtins(name);
    if (result) return result;
    
    conditional_mutex_locker_t lock(selLock, shouldLock);
    //插入sel到namedSelectors
    auto it = namedSelectors.get().insert(name);
    if (it.second) {
        // No match. Insert.
        *it.first = (const char *)sel_alloc(name, copy);
    }
    return (SEL)*it.first;
}

sel本身是一个带地址的字符串,selsels[i]在内存中的地址不一样的。所以要把sels[i]的地址改成sel的。

图1.2.2.png

3. 发现类,修复未解决的问题

    bool hasDyldRoots = dyld_shared_cache_some_image_overridden();

    for (EACH_HEADER) {
        if (! mustReadClasses(hi, hasDyldRoots)) {
            // Image is sufficiently optimized that we need not call readClass()
            //镜像得到了充分优化,因此我们不需要调用readClass()
            continue;
        }

        //获取class列表,就是从`mach-o`中读取`__objc_classlist`所有的类,然后放到`classlist`
        //并且从`mach-o`中获取了`__objc_classlist`的count
        classref_t const *classlist = _getObjc2ClassList(hi, &count);

        //判断是不是bundle的头文件
        bool headerIsBundle = hi->isBundle();
        //判断是不是预先优化过的类
        bool headerIsPreoptimized = hi->hasPreoptimizedClasses();

        //遍历整张classlist表
        for (i = 0; i < count; i++) {
            //获取类
            Class cls = (Class)classlist[i];
            //读取编译器编写的类和元类,并返回一个类的新指针
            Class newCls = readClass(cls, headerIsBundle, headerIsPreoptimized);

            //这里是懒加载用的
            //初始化所有懒加载的类的空间,但是并不会初始化这个类,所以懒加载的类也不会有数据
            if (newCls != cls  &&  newCls) {
                // Class was moved but not deleted. Currently this occurs 
                // only when the new class resolved a future class.
                // Non-lazily realize the class below.
                resolvedFutureClasses = (Class *)
                    realloc(resolvedFutureClasses, 
                            (resolvedFutureClassCount+1) * sizeof(Class));
                resolvedFutureClasses[resolvedFutureClassCount++] = newCls;
            }
        }
    }
  • classlist : 从mach-o可执行文件中读取到__objc_classlist所有的类,然后放到classlist

  • 遍历classlist

    • Class cls : 读取每一个cls。这里因为读的是mach-o拿过来的类,其实只是一个内存地址,还没有一个类的名字。
    • Class newCls : 这里经过readClass的处理,返回的是cls被重新处理过的一个指针。这里才是有了libobjc的参与,有了一个名字。

验证 :

验证一下上面说的cls开始只有地址,readClass后才有地址和名字。断点挂在Class cls的下面一行,随便创建一个继承于NSObject的类,我之前创建了JDPerson。然后运行。

结果 :

图1.3.1.png

4. 修复重新映射的类

//修复重新映射的类
    //类列表和非懒加载类列表仍未重新映射
    //类和父类的关联被重新映射,用于信息调度
    if (!noClassesRemapped()) {
        for (EACH_HEADER) {
            //      function name                 content type     section name
            //GETSECT(_getObjc2ClassRefs,           Class,           "__objc_classrefs");
            Class *classrefs = _getObjc2ClassRefs(hi, &count);
            for (i = 0; i < count; i++) {
                remapClassRef(&classrefs[i]);
            }
            //GETSECT(_getObjc2SuperRefs,           Class,           "__objc_superrefs");
            // fixme why doesn't test future1 catch the absence of this?
            classrefs = _getObjc2SuperRefs(hi, &count);
            for (i = 0; i < count; i++) {
                remapClassRef(&classrefs[i]);
            }
        }
    }
  • _getObjc2ClassRefs_getObjc2SuperRefs : 分别是获取mach-o中的__objc_classrefs(类的引用)和__objc_superrefs(父类的引用)。

  • remapClassRef : 重新映射类的引用。就是修复类的引用,以防引用的类被重新分配内存或是一个被忽略的弱链接类。而且注释说了,懒加载的类需要remapClassRef

5. 修复旧的objc_msgSend_fixup调用站点

//修复旧的objc_msgSend_fixup调用站点
    for (EACH_HEADER) {
        //        function name                 content type     section name
        //GETSECT(_getObjc2MessageRefs,         message_ref_t,   "__objc_msgrefs");
        //获取`mach-o`中的__objc_msgrefs信息段,和数量
        message_ref_t *refs = _getObjc2MessageRefs(hi, &count);
        //没有话就跳出for循环
        if (count == 0) continue;
        
        if (PrintVtables) {
            _objc_inform("VTABLES: repairing %zu unsupported vtable dispatch "
                         "call sites in %s", count, hi->fname());
        }
        //修复旧的vtable。
        for (i = 0; i < count; i++) {
            fixupMessageRef(refs+i);
        }
    }

没什么特别要说的,而且第一遍运行进来根本不走for循环里面的代码

6. 发现协议。修复协议引用。

    for (EACH_HEADER) {
        extern objc_class OBJC_CLASS_$_Protocol;
        //protocol和类的结构是类似的。
        Class cls = (Class)&OBJC_CLASS_$_Protocol;
        ASSERT(cls);
        //拿到协议名称和协议的映射,放到protocol_map中
        NXMapTable *protocol_map = protocols();
        bool isPreoptimized = hi->hasPreoptimizedProtocols();

        if (launchTime && isPreoptimized && cacheSupportsProtocolRoots) {
            if (PrintProtocols) {
                _objc_inform("PROTOCOLS: Skipping reading protocols in image: %s",
                             hi->fname());
            }
            continue;
        }

        bool isBundle = hi->isBundle();
        //        function name                    content type            section name
        //GETSECT(_getObjc2ProtocolList,        protocol_t * const,    "__objc_protolist");
        //获取mach-o文件中的`__objc_protolist`和它里面内容的数量
        protocol_t * const *protolist = _getObjc2ProtocolList(hi, &count);
        //遍历读取编译器中的协议,然后放到`protocol_map`里面
        for (i = 0; i < count; i++) {
            readProtocol(protolist[i], cls, protocol_map, 
                         isPreoptimized, isBundle);
        }
    }
  • NXMapTable *protocol_map : 拿到协议名称和协议的映射,放到protocol_map中。

  • readProtocol : 遍历读取mach-o__objc_protolist字段中的协议列表,然后放到protocol_map里面。

7. 修复@protocol的引用。

    //修复协议引用
    for (EACH_HEADER) {
        //在启动的时候,预先优化的镜像引用指向了一个协议的共享缓存区。
        //可以跳过启动的检查,但是在镜像的共享缓存加载之后,必须要访问@protocol的引用
        if (launchTime && cacheSupportsProtocolRoots && hi->isPreoptimized())
            continue;
        //      function name                   content type        section name
        //GETSECT(_getObjc2ProtocolRefs,        protocol_t *,    "__objc_protorefs");
        //这个和上面的不一样,上面拿的是protocollist,这里拿的是`__objc_protorefs`
        protocol_t **protolist = _getObjc2ProtocolRefs(hi, &count);
        //循环着重新映射协议引用
        for (i = 0; i < count; i++) {
            remapProtocolRef(&protolist[i]);
        }
    }
  • 这里是协议的引用,获取的是mach-o中的__objc_protorefs和上面的list不一样。

  • remapProtocolRef : 比较当前的协议和协议列表中的协议是否一样,不同的话,换成协议列表中的协议。

8. 分类的处理

    //发现分类。只有在分类的初始化完成,并且附着到了类上之后才需要做。
    //或者在运行时发现了分类,那么分类的处理就要推迟到
    //`_dyld_objc_notify_register`调用完成后的第一次`load_images`的调用。
    if (didInitialAttachCategories) {
        for (EACH_HEADER) {
            load_categories_nolock(hi);
        }
    }
  • 处理分类是分情况的。

    • 可以在分类的初始化完成,并且附着到类上以后进行分类的处理。
    • 或者处于运行时中间进来的分类,就要等到_dyld_objc_notify_register中的load_images第一次调用完成后再处理。
  • 另外,如果在当前线程完成分类的处理之前,其他的线程调用了新的分类代码,那么发现新分类的这个动作就要推迟,避免潜在的竞争。

9. 实现非懒加载的类

//实现非懒加载的类(用于+load方法和静态实例)
    for (EACH_HEADER) {
        //      function name                     content type             section name
        //GETSECT(_getObjc2NonlazyClassList,    classref_t const,      "__objc_nlclslist");
        //获取`mach-o`中的`__objc_nlclslist`列表内容(非懒加载的类),放入`classlist`列表。
        classref_t const *classlist = 
            _getObjc2NonlazyClassList(hi, &count);
        
        for (i = 0; i < count; i++) {
            Class cls = remapClass(classlist[i]);
            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());
                }
            }
            //实现这个类。前面的发现类的步骤中,并没有真正的实现类,只是给了内存地址和名称,并没有处理类的数据
            realizeClassWithoutSwift(cls, nil);
        }
    }
  • 获取的是mach-o中的__objc_nlclslist列表,也即是非懒加载的类的列表。

  • addClassTableEntry() : 把非懒加载的类加入到类的列表中。如果addMeta = trye,那么会自动把类的元类加载进去。但是不会重复的进行加载,已经加载过的类就不会再次加载到表里面。

  • realizeClassWithoutSwift() : 实现一个类。前面的类的发现中,只给了类一个内存地址和一个名称,没有把具体的数据内容给到类中,所以这里需要实现类。

10. 实现一些新解析的类,防止CoreFoundation操作了它们。

if (resolvedFutureClasses) {
        for (i = 0; i < resolvedFutureClassCount; i++) {
            Class cls = resolvedFutureClasses[i];
            if (cls->isSwiftStable()) {
                _objc_fatal("Swift class is not allowed to be future");
            }
            //实现这些类
            realizeClassWithoutSwift(cls, nil);
            cls->setInstancesRequireRawIsaRecursively(false/*inherited*/);
        }
        free(resolvedFutureClasses);
    }
  • resolvedFutureClasses : 在3中见过的,也就是类发现了,但是没有被处理,或者有一些问题,就会放到这里。

  • realizeClassWithoutSwift : 把这些没发现的类,还有没有处理好的类全部都实现掉。

二、readClass

把一个只有地址的类赋予了名称,这是在第三步中见过的函数,也是实现了类的加载的重点,看名称是"读取类"的意思,其实就是将mach-o中的一段有关类的内存读取出来。

/***********************************************************************
* readClass
* Read a class and metaclass as written by a compiler.
* Returns the new class pointer. This could be: 
* - cls
* - nil  (cls has a missing weak-linked superclass)
* - something else (space for this class was reserved by a future class)
*
* Note that all work performed by this function is preflighted by 
* mustReadClasses(). Do not change this function without updating that one.
*
* Locking: runtimeLock acquired by map_images or objc_readClassPair
**********************************************************************/
Class readClass(Class cls, bool headerIsBundle, bool headerIsPreoptimized)
{
    //拿到cls的名字
    const char *mangledName = cls->mangledName();
    
    //只有传进来的cls(这时候只是内存地址)在取不到父类(只有根类NSObject才可能为nil)
    //或者是一个弱链接的父类的情况下,才返回nil
    if (missingWeakSuperclass(cls)) {
        // No superclass (probably weak-linked). 
        // Disavow any knowledge of this subclass.
        if (PrintConnecting) {
            _objc_inform("CLASS: IGNORING class '%s' with "
                         "missing weak-linked superclass", 
                         cls->nameForLogging());
        }
        addRemappedClass(cls, nil);
        cls->superclass = nil;
        return nil;
    }
    
    cls->fixupBackwardDeployingStableSwift();

    Class replacing = nil;
    //判断是否是后期进行处理的类
    //正常的类是不会进入这个判断的,只有futureClass也就是后期类才满足条件
    if (Class newCls = popFutureNamedClass(mangledName)) {
        // This name was previously allocated as a future class.
        // Copy objc_class to future class's struct.
        // Preserve future's rw data block.
        
        if (newCls->isAnySwift()) {
            _objc_fatal("Can't complete future class request for '%s' "
                        "because the real class is too big.", 
                        cls->nameForLogging());
        }
        
        class_rw_t *rw = newCls->data();
        const class_ro_t *old_ro = rw->ro();
        memcpy(newCls, cls, sizeof(objc_class));
        rw->set_ro((class_ro_t *)newCls->data());
        newCls->setData(rw);
        freeIfMutable((char *)old_ro->name);
        free((void *)old_ro);
        
        addRemappedClass(cls, newCls);
        
        replacing = cls;
        cls = newCls;
    }
    
    //replacing都没有赋值,还是nil,不是futureClass的话,replacing = nil
    //所以这里根本进不去
    if (headerIsPreoptimized  &&  !replacing) {
        // class list built in shared cache
        // fixme strict assert doesn't work because of duplicates
        // ASSERT(cls == getClass(name));
        ASSERT(getClassExceptSomeSwift(mangledName));
    } 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;
}

其实里面很多都是future_class才能满足的条件,一般的类都不是future_class,那么可以做到起名字的功能的就最开始的那一句,另外cls还被插入到了两个表里面。

  • mangledName() : 就是从数据里面拿到名字。
const char *mangledName() { 
        // fixme can't assert locks here
        ASSERT(this);
        //如果是一个实现的类或者是一个future_class,直接就从实例化的对象的ro里面取名字
        if (isRealized()  ||  isFuture()) {
            return data()->ro()->name;
        } else {
            //如果既没实现,也不是future_class,直接从mach-o的data中拿名字
            return ((const class_ro_t *)data())->name;
        }
    }
  • addNamedClass() : 把cls插入了gdb_objc_realized_classes表。这个表上面说过,只要不是dyld的共享缓存中的类并且已命名,都可以存进来。
/***********************************************************************
* addNamedClass
* Adds name => cls to the named non-meta class map.
* Warns about duplicate class names and keeps the old mapping.
* Locking: runtimeLock must be held by the caller
**********************************************************************/
static void addNamedClass(Class cls, const char *name, Class replacing = nil)
{
    runtimeLock.assertLocked();
    Class old;
    //这里也不用看了,replacing = nil
    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 {
        //这里进来了,把name和cls插入到了一个表里面,这个表最上面就见过。
        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());
}
  • addClassTableEntry() : 又把cls插入到了allocatedClasses表。allocatedClasses表是一个所有的类都能插入的表。而且如果设置addMeta=true,连cls的父类也会这个时候一起插入这张表。
/***********************************************************************
* addClassTableEntry
* Add a class to the table of all classes. If addMeta is true,
* automatically adds the metaclass of the class as well.
* Locking: runtimeLock must be held by the caller.
**********************************************************************/
static void
addClassTableEntry(Class cls, bool addMeta = true)
{
    runtimeLock.assertLocked();

    // This class is allowed to be a known class via the shared cache or via
    // data segments, but it is not allowed to be in the dynamic table already.
    auto &set = objc::allocatedClasses.get();

    ASSERT(set.find(cls) == set.end());

    if (!isKnownClass(cls))
        //set就是allocatedClasses表,
        //这个是最开始_objc_init的时候,runtime_init()开辟的内存空间建立的表
        //前面说过了
        set.insert(cls);
    if (addMeta)
        addClassTableEntry(cls->ISA(), false);
}
  • 另外,if (Class newCls = popFutureNamedClass(mangledName))这句判断的确平常是不走的,其实只看这里面的最后一句,cls = newCls,然后对比第一块内容的发现类中的if (newCls != cls && newCls)可以发现,正常情况下cls传进readClass(),返回出来的还是cls,不会出现newCls != cls的情况,只有进入到了if (Class newCls = popFutureNamedClass(mangledName))才会出现clsnewCls替换过的情况,才会走if (newCls != cls && newCls)中的代码。所以我猜测提到的future_class可能包括懒加载的类。

总结 :

  1. readClass第一个做的事情是从类的数据中拿到名字
  2. 第二个要做的事情是把类插入到gdb_objc_realized_classes表里面,这张表除了dyld的共享缓存中的类,其他的已命名的类都可以存。
  3. 第三个要做的事情是把类插入到allocatedClasses,这张表是_objc_initruntime_init()中开辟内存建立的表,存储所有的类信息,包括元类。
  4. 综上三点,readClass就是把mach-o中的类插入到了内存中的表里,实现了加载(load)可执行文件的功能。但是,readClass对正常的类只从mach-odata中拿到了name,并没有取其他的mach-o中的数据。

三、realizeClassWithoutSwift

在(一)中查看_read_images的流程,只有第9步才和正常的类发生了关联,那么类的加载可能就在这里面。上面有源码我就不贴了,找到类的实现。也就是realizeClassWithoutSwift。进入它查看它的实现思路。因为也比较长,所以分步骤来看。

首先看一下公共的部分,第一个是三个变量的定义

    //定义一个rw
    class_rw_t *rw;
    //定义一个父类
    Class supercls;
    //定义一个元类
    Class metacls;

第二个公共区域是两个if判断

    //如果cls是nil,直接返回nil
    if (!cls) return nil;
    //如果类已经实现过了,直接返回这个类对象
    if (cls->isRealized()) return cls;
    //确定一下,这个类是正常的映射过来了。需要重新映射的找到上面那个修复重新映射的类那里去
    ASSERT(cls == remapClass(cls));

第1步 : rw的创建,读取clsdata数据

{
    //获取类里面的data,但是看类型,类的结构那节见过,应该是`class_rw_t`,
    //但是真正从镜像加载来看,类是先有的`class_ro_t`的数据
    auto ro = (const class_ro_t *)cls->data();
    //判断是不是元类
    auto isMeta = ro->flags & RO_META;
    if (ro->flags & RO_FUTURE) {
        // This was a future class. rw data is already allocated.
        //看上面这句注释,future class,上面猜想过,后期实现类可能是懒加载的类,先不看
        //看下面常规的
        rw = cls->data();
        ro = cls->data()->ro();
        ASSERT(!isMeta);
        cls->changeInfo(RW_REALIZED|RW_REALIZING, RW_FUTURE);
    } else {
        // Normal class. Allocate writeable class data.
        //看这里,这里是常规的类,注释说分配可写入的类数据,重点是数据的分配
        //给上面定义的rw分配一块内存空间,并且指定了内存空间的数据结构类型是class_rw_t
        rw = objc::zalloc();
        //设置rw的ro是上面cls从data中取出来的ro
        rw->set_ro(ro);
        rw->flags = RW_REALIZED|RW_REALIZING|isMeta;
        //设置cls的rw
        cls->setData(rw);
    }

第2步 : 完善继承链。

这两句可以看到是cls取到了自己的父类和元类,然后又进行了第1步中,数据的读取,并且设置rwro,再把设置好rorw设置到类里面。

    supercls = realizeClassWithoutSwift(remapClass(cls->superclass), nil);
    metacls = realizeClassWithoutSwift(remapClass(cls->ISA()), nil);

然后把superclsmetacls分别放入Class中的superClassisa。完善类的结构。保证isasuperClass的完整性。

如果clsNSObject的时候,就会发现superclsnil,则第一个的递归就结束了。

第二个的递归,关于元类的,可以看一下remapClass的实现。它也是查询表中是否已经存在cls->ISA()的值,如果存在的话,则会直接返回一个nil,所以也会结束递归。

    // Update superclass and metaclass in case of remapping
    cls->superclass = supercls;
    cls->initClassIsa(metacls);

除了对父类和元类的设置,还要设置子类的信息。非NSObject的类,会把自己放入父类的子类列表里面。而NSObject会把自己加入到根类上面。

    // Connect this class to its superclass's subclass lists
    if (supercls) {
        addSubclass(supercls, cls);
    } else {
        addRootClass(cls);
    }

这个递归一直会到NSObject为止,因为cls->superclass作为参数cls递归到NSObject的时候,clsnil,根据公共区域的if条件,会直接返回nil。也就是那张神图的路线。

第3步 : methodizeClass

这个就要进去看源码了,源码上我加了注释。

/**
 修复类的方法列表。协议列表,属性列表。
 将没有附着到类上的分类,都附着到类上面来。
 */
static void methodizeClass(Class cls, Class previously)
{
    runtimeLock.assertLocked();

    
    //从cls里面拿到rw,从rw里面拿到ro,再拿到rw的额外信息rwe。
    bool isMeta = cls->isMetaClass();
    auto rw = cls->data();
    auto ro = rw->ro();
    auto rwe = rw->ext();

    // Methodizing for the first time
    if (PrintConnecting) {
        _objc_inform("CLASS: methodizing class '%s' %s", 
                     cls->nameForLogging(), isMeta ? "(meta)" : "");
    }

    // Install methods and properties that the class implements itself.
    //装载cls类自己实现的方法和属性
    method_list_t *list = ro->baseMethods();
    if (list) {
        //准备一下方法列表
        prepareMethodLists(cls, &list, 1, YES, isBundleClass(cls));
        //把methodList贴到rew的methods里面。
        //下面的都是同理,我就不写注释了
        if (rwe) rwe->methods.attachLists(&list, 1);
    }

    property_list_t *proplist = ro->baseProperties;
    if (rwe && proplist) {
        rwe->properties.attachLists(&proplist, 1);
    }

    protocol_list_t *protolist = ro->baseProtocols;
    if (rwe && protolist) {
        rwe->protocols.attachLists(&protolist, 1);
    }

    // Root classes get bonus method implementations if they don't have 
    // them already. These apply before category replacements.
    //如果cls是根元类
    //如果根类还没有方法实现,则会获得额外的方法实现。这些适用于分类替换之前。
    if (cls->isRootMetaclass()) {
        // root metaclass
        addMethod(cls, @selector(initialize), (IMP)&objc_noop_imp, "", NO);
    }

    // Attach categories.
    //贴分类的东西
    if (previously) {
        if (isMeta) {
            objc::unattachedCategories.attachToClass(cls, previously,
                                                     ATTACH_METACLASS);
        } else {
            // When a class relocates, categories with class methods
            // may be registered on the class itself rather than on
            // the metaclass. Tell attachToClass to look for those.
            objc::unattachedCategories.attachToClass(cls, previously,
                                                     ATTACH_CLASS_AND_METACLASS);
        }
    }
    objc::unattachedCategories.attachToClass(cls, cls,
                                             isMeta ? ATTACH_METACLASS : ATTACH_CLASS);

#if DEBUG
    // Debug: sanity-check all SELs; log method list contents
    for (const auto& meth : rw->methods()) {
        if (PrintConnecting) {
            _objc_inform("METHOD %c[%s %s]", isMeta ? '+' : '-', 
                         cls->nameForLogging(), sel_getName(meth.name));
        }
        ASSERT(sel_registerName(sel_getName(meth.name)) == meth.name); 
    }
#endif
}

那么理解这里的话,就要先知道cls中的ro到底是什么,rw又是什么。

  • ro : 其实就是Class读取bits.data的一种形式。全名就是readOnly,也就是只读。证明这部分不是我们可以再对类进行操作的。它在编译时期就已经确认好了类的名称、方法、协议、实例变量等等信息。
    因为它的readOnly属性,所以被称为Clean Memory,意为加载之后不会发生更改的内存。

  • rw : readWrite,可读可写。具有动态性。除了只能读方法、属性、协议,也可以写入。因为它具有动态性,又可读可写,被称为dirty memory,意为在进程运行时发生改变的内存。

  • rwe : 类的额外信息。就是原来的rw存储的内容。搬家到这里是因为被更改方法的类比较少。

然后再来看其源码。大致可以分成两个部分。一个部分是attachLists,各种贴。另一部分就是分类相关的。

3.1 attachLists
void attachLists(List* const * addedLists, uint32_t addedCount) {
        if (addedCount == 0) return;

        //多个列表对多个列表
        if (hasArray()) {
            // many lists -> many lists
            //拿到原有数组的count
            uint32_t oldCount = array()->count;
            //新数组的count = 旧有数组的count + 添加的count
            uint32_t newCount = oldCount + addedCount;
            //根据新数组的count拿一块新的内存
            setArray((array_t *)realloc(array(), array_t::byteSize(newCount)));
            //把数组的大小更新
            array()->count = newCount;
            //内存移动
            memmove(array()->lists + addedCount, array()->lists, 
                    oldCount * sizeof(array()->lists[0]));
            //内存copy
            memcpy(array()->lists, addedLists, 
                   addedCount * sizeof(array()->lists[0]));
        }
        //没有列表,attach完成以后变成1个列表
        else if (!list  &&  addedCount == 1) {
            // 0 lists -> 1 list
            //直接把列表添加到第一个就行
            list = addedLists[0];
        }
        //开始只有一张列表,attach后变成多张列表
        else {
            // 1 list -> many lists
            //旧有列表
            List* oldList = list;
            //旧有列表的大小
            uint32_t oldCount = oldList ? 1 : 0;
            //新列表的大小
            uint32_t newCount = oldCount + addedCount;
            //设置新列表数组的内存大小
            setArray((array_t *)malloc(array_t::byteSize(newCount)));
            //更新数组的大小
            array()->count = newCount;
            //把旧有列表放到后面去
            if (oldList) array()->lists[addedCount] = oldList;
            //内存copy
            memcpy(array()->lists, addedLists, 
                   addedCount * sizeof(array()->lists[0]));
        }
    }

可以看出有三种情况,

  • 多对多。即类中有多个列表。又添加了多个列表。
  • 0到1。类中没有列表,新增一个列表。
  • 1到多。类中原来只有一个列表,增加了很多列表。

关于这个memmovememcpy

memmove : 多对多里面出现。就是把老的那一块列表的内存,移动到addCount下标后面开始,占用大小是旧有的数量 * 单个list的大小。

memcpy : 从开始的位置开始放入新的list,大小是添加的数量 * 单个list的大小。

其实这里面主要发现的问题在rwe身上,因为全部的数据操作都是rwe在填充数据。也就是说,这一步其实是实现了ro中的数据转移到rwe中,以后再添加的分类,方法,协议,全部都添加到rwe中。

目的就是为了保持ro的纯净。操作全部都在rwe中进行。

步骤就是 :

  1. 先把类原有的ro放入到rwe中,也就是从0到1。
  2. 然后如果添加一个分类,相当于从1到多。rwe就有了类的list分类的list
  3. 如果再添加新的分类或者其他,就是多到多。rwe就有了类的list多个分类的list

关于这个的解释,可以看下图。

图片.png

另外可以看到,无论是method还是protocol等等,用的都是attachLists,也就是说,它们的结构大体是相近的。

看他们的列表 :

method_list_t :

struct method_list_t : entsize_list_tt

property_list_t :

struct property_list_t : entsize_list_tt

protocol_list_t

struct protocol_list_t

只有protocol_list_t貌似不一样,但是protocol_list_t里面还有一个protocol_ref_t

其实它们都是一样的思路,就是二维的数组。看rwe的结构里面

struct class_rw_ext_t {
    const class_ro_t *ro;
    method_array_t methods;
    property_array_t properties;
    protocol_array_t protocols;
    char *demangledName;
    uint32_t version;
};

methods是以method_array_t结构存在的。
properties是以property_array_t结构存在的。
protocols是以protocol_array_t结构存在的。

也就是说都是如下的结构 : xxx可以是methodpropertyprotocol

图片.png

一种二维数组的形式存储在rwe中。

四、总结

现在知道了普通的类是如何从mach-o加载内存中的了。

  1. readClass发现了类,并且给这个类命名。然后插入表中。
  • 插入gdb_objc_realized_classes表,这个表存储被命名过的,且不是dyld共享缓冲中的类。
  • 插入allocatedClasses表,所有的类都要插入这个表。
  1. realizeClassWithoutSwift完成了类的其他信息从mach-o加载到内存。
  • ro赋值,创建rw
  • cls的继承链变完整,包括父类,元类,子类。
  • methodizeClass : 把ro中的数据复制一份到rwe中,保证ro的纯净。
    以后添加的分类、方法、协议等信息,都给rwe进行attachList

你可能感兴趣的:(第十五节—libobjc探索类的加载(一))