iOS 底层原理-类的加载(上)

在上一篇文章 iOS 底层 dyld 与 objc 的关联 中分析了 dyldobjc 是如何关联上的,下面来了解下类的相关信息是如何加载到内存上的。

_dyld_objc_notify_register 注册回调中有带三个参数,我们重点看下 map_imagesload_images

  • map_images:管理文件中和动态库中的所有符号,即class、protocol、selector、category等

  • load_images:加载执行load方法

其中代码通过编译转换成可执行文件(Mach-o),然后将 Mach-o 读取到内存,如下所示

map_images 加载镜像文件到内存

map_images 主要是将 mach-o 文件加载到内存中

    1. 进入 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);
}
    1. 进入 map_images_nolock 的源码,查看其具体的实现

源码很长,主要是查找 mach-o 中的 Objective-C 数据,从源码实现中可以看出,_read_images 是关键代码,下面我们着重看下它的实现

_read_images 源码

源码很长,主要功能分为以下部分:

    1. 条件控制进行的一次加载
    1. 修复预编译阶段的@selector的混乱问题
    1. 错误混乱的类处理
    1. 修复重映射一些没有被镜像文件加载进来的类
    1. 修复一些消息
    1. 当类里面有协议时,readProtocol
    1. 修复没有被加载的协议
    1. 分类处理
    1. 类的加载处理
    1. 没有被处理的类,优化那些被侵犯的类
1. 条件控制进行的一次加载

主要是通过 NXCreateMapTable 创建表,存放了已经命名的类的信息,目的是为了方便查找、快捷

    if (!doneOnce) {
        doneOnce = YES;
        launchTime = YES;

#if SUPPORT_NONPOINTER_ISA
        // Disable non-pointer isa under some conditions.

# if SUPPORT_INDEXED_ISA
        // Disable nonpointer isa if any image contains old Swift code
        for (EACH_HEADER) {...}
# endif

# if TARGET_OS_OSX
        // Disable non-pointer isa if the app is too old
        // (linked before OS X 10.11)
        if (dyld_get_program_sdk_version() < DYLD_MACOSX_VERSION_10_11) {...}

        // Disable non-pointer isa if the app has a __DATA,__objc_rawisa section
        // New apps that load old extensions may need this.
        for (EACH_HEADER) {...}
# endif

#endif

        if (DisableTaggedPointers) {
            disableTaggedPointers();
        }
        
        initializeTaggedPointerObfuscator();

        if (PrintConnecting) {
            _objc_inform("CLASS: found %d classes during launch", totalClasses);
        }

        // namedClasses
        // Preoptimized classes don't go in this table.
        // 4/3 is NXMapTable's load factor
        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 官方文档有说明

// This is a misnomer: gdb_objc_realized_classes is actually a list of 
// named classes not in the dyld shared cache, whether realized or not.
// gdb_objc_realized_classes 实际上是一张已经命名的类的表,无论它是否实现都不在 dyld 共享缓存中
NXMapTable *gdb_objc_realized_classes;  // exported for debuggers in objc-gdb.h
2. 修复预编译阶段的@selector的混乱问题

主要是通过通过 _getObjc2SelectorRefs 拿到 Mach_O 中的静态段__objc_selrefs,保证 sel_registerNameNoLock 获取的 sel_getObjc2SelectorRefs 中的 sel 一致

    // Fix up @selector references
    // sel 不是简单的字符串,而是带地址的字符串
    static size_t UnfixedSelectors;
    {
        mutex_locker_t lock(selLock);
        for (EACH_HEADER) {
            if (hi->hasPreoptimizedSelectors()) continue;

            bool isBundle = hi->isBundle();
            // 通过 _getObjc2SelectorRefs 拿到 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 操作,即将 sel 添加到 namedSelectors 哈希表中
                SEL sel = sel_registerNameNoLock(name, isBundle);
                // 当 sel 与 sels[i] 地址不一致时,需要调整为一致的
                if (sels[i] != sel) {
                    sels[i] = sel;
                }
            }
        }
    }

其中 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 插入
    auto it = namedSelectors.get().insert(name);
    if (it.second) {
        // No match. Insert.
        *it.first = (const char *)sel_alloc(name, copy);
    }
    return (SEL)*it.first;
}

关键代码 auto it = namedSelectors.get().insert(name);sel 插入 namedSelectors 哈希表。其中 sel 并不是简单的字符串,是带地址的字符串。我们可以通过断点调试验证,如下

3. 错误混乱的类处理

主要是从 Mach-O 中取出所有类,再遍历进行处理

    // Discover classes. Fix up unresolved future classes. Mark bundle classes.
    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()
            continue;
        }

        // 从 Mach-O 中获取静态段 __objc_classlist,是一个 classref_t 类型的指针(编译后的类列表中)
        classref_t const *classlist = _getObjc2ClassList(hi, &count);

        bool headerIsBundle = hi->isBundle();
        bool headerIsPreoptimized = hi->hasPreoptimizedClasses();

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

其核心代码是 readClass,在未执行 readClass 前后, cls 有什么变化呢,我们打个断点,调试如下

从上面调试中可以看到,在 readClass 前,cls 还只是一个地址,readClass 之后,cls 获取就变成了名字。到此,类的信息仅仅存储了类的地址和名字

4. 修复重映射一些没有被镜像文件加载进来的类

主要是将未映射的 ClassSuper Class 进行重映射

// Fix up remapped classes
    // Class list and nonlazy class list remain unremapped.
    // Class refs and super refs are remapped for message dispatching.
    
    if (!noClassesRemapped()) {
        for (EACH_HEADER) {
            // 获取 Mach-O 中的静态段 __objc_classrefs(类的引用)
            Class *classrefs = _getObjc2ClassRefs(hi, &count);
            for (i = 0; i < count; i++) {
                remapClassRef(&classrefs[I]);
            }
            // fixme why doesn't test future1 catch the absence of this?
            // 获取 Mach-O 中的静态段 __objc_superrefs(父类的引用)
            classrefs = _getObjc2SuperRefs(hi, &count);
            for (i = 0; i < count; i++) {
                remapClassRef(&classrefs[I]);
            }
        }
    }

从源码注释中,可以看到需要被 remapClassRef 的类是懒加载类(没有手动实现 +load),所以调试的时候这部分代码是没有走的

5. 修复一些消息

主要是遍历 refs,通过 fixupMessageRef 将函数指针进行注册,并 fix 为新的函数指针

    // Fix up old objc_msgSend_fixup call sites
    for (EACH_HEADER) {
        // 通过 _getObjc2MessageRefs 获取 Mach-O 的静态段 __objc_msgrefs
        message_ref_t *refs = _getObjc2MessageRefs(hi, &count);
        if (count == 0) continue;

        if (PrintVtables) {
            _objc_inform("VTABLES: repairing %zu unsupported vtable dispatch "
                         "call sites in %s", count, hi->fname());
        }
        // 遍历
        for (i = 0; i < count; i++) {
            fixupMessageRef(refs+i);
        }
    }
6. 当类里面有协议时:readProtocol

主要是将从静态段获取的协议列表,循环遍历添加到协议哈希表中

    bool cacheSupportsProtocolRoots = sharedCacheSupportsProtocolRoots();

    // Discover protocols. Fix up protocol refs.
    for (EACH_HEADER) {
        extern objc_class OBJC_CLASS_$_Protocol;
        Class cls = (Class)&OBJC_CLASS_$_Protocol;
        ASSERT(cls);
        // 获取哈希表
        NXMapTable *protocol_map = protocols();
        bool isPreoptimized = hi->hasPreoptimizedProtocols();

        // Skip reading protocols if this is an image from the shared cache
        // and we support roots
        // Note, after launch we do need to walk the protocol as the protocol
        // in the shared cache is marked with isCanonical() and that may not
        // be true if some non-shared cache binary was chosen as the canonical
        // definition
        if (launchTime && isPreoptimized && cacheSupportsProtocolRoots) {
            if (PrintProtocols) {
                _objc_inform("PROTOCOLS: Skipping reading protocols in image: %s",
                             hi->fname());
            }
            continue;
        }

        bool isBundle = hi->isBundle();

        // 通过 _getObjc2ProtocolList 获取到 Mach-O 中的静态段 __objc_protolist 协议列表
        protocol_t * const *protolist = _getObjc2ProtocolList(hi, &count);
        // 遍历协议列表
        for (i = 0; i < count; i++) {
            // 将协议添加到 protocol_map 哈希表中
            readProtocol(protolist[i], cls, protocol_map, 
                         isPreoptimized, isBundle);
        }
    }
7. 修复没有被加载的协议

通过获取静态段 _getObjc2ProtocolRefs(与上面的 _getObjc2ProtocolList 不一样),遍历需要修复的协议,通过 remapProtocolRef 比较当前协议和协议列表中的同一个内存地址的协议是否相同,如果不同则替换

    // Fix up @protocol references
    // Preoptimized images may have the right 
    // answer already but we don't know for sure.
    for (EACH_HEADER) {
        // At launch time, we know preoptimized image refs are pointing at the
        // shared cache definition of a protocol.  We can skip the check on
        // launch, but have to visit @protocol refs for shared cache images
        // loaded later.
        if (launchTime && cacheSupportsProtocolRoots && hi->isPreoptimized())
            continue;
        // 获取到 Mach-O 的静态段 __objc_protorefs
        protocol_t **protolist = _getObjc2ProtocolRefs(hi, &count);
        // 遍历
        for (i = 0; i < count; i++) {
            // 比较当前协议和协议列表中的同一个内存地址的协议是否相同,如果不同则替换
            remapProtocolRef(&protolist[I]);
        }
    }

remapProtocolRef 的源码实现如下

/***********************************************************************
* remapProtocolRef
* Fix up a protocol ref, in case the protocol referenced has been reallocated.
* Locking: runtimeLock must be read- or write-locked by the caller
**********************************************************************/
static size_t UnfixedProtocolReferences;
static void remapProtocolRef(protocol_t **protoref)
{
    runtimeLock.assertLocked();

    // 取协议列表中的实时协议指针
    protocol_t *newproto = remapProtocol((protocol_ref_t)*protoref);
    if (*protoref != newproto) {
        // 如果当前协议与同一内存地址协议不同,则替换
        *protoref = newproto;
        UnfixedProtocolReferences++;
    }
}
8. 分类处理

处理分类,仅在完成初始类别并将数据加载到类后才执行,对于启动时出现的分类,将分类的发现推迟推迟到对 _dyld_objc_notify_register 的调用完成后的第一个 load_images 调用完成

    // Discover categories. Only do this after the initial category
    // attachment has been done. For categories present at startup,
    // discovery is deferred until the first load_images call after
    // the call to _dyld_objc_notify_register completes. rdar://problem/53119145
    if (didInitialAttachCategories) {
        for (EACH_HEADER) {
            load_categories_nolock(hi);
        }
    }
9. 类的加载处理

主要是实现类的加载,实现非懒加载类。源码如下

    // Realize non-lazy classes (for +load methods and static instances)
    // 实现非懒加载类(实现了 +load 方法或者 静态实例)
    for (EACH_HEADER) {
        // 获取 Mach-O 的静态段 __objc_nlclslist (非懒加载类表)
        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());
                }
                // fixme also disallow relocatable classes
                // We can't disallow all Swift classes because of
                // classes like Swift.__EmptyArrayStorage
            }
            // 实现当前的类,因为前面 readClass 读取到内存的仅仅只有地址+名称,类的 data 数据并没有加载出来
            // 实现所有非懒加载的类(实例化类对象的一些信息,例如rw)
            realizeClassWithoutSwift(cls, nil);
        }
    }
  • _getObjc2NonlazyClassList 获取 Mach-O 的静态段 __objc_nlclslist(非懒加载类表)
  • addClassTableEntry 将非懒加载类插入类表,存储到内存,如果已经添加就不会载添加,需要确保整个结构都被添加
  • realizeClassWithoutSwift 实现当前的类,在前面 readClass 读取到内存的仅仅只有地址+名称,类的 data 数据还没有加载
10. 没有被处理的类,优化那些被侵犯的类

主要是实现没有被处理的类,优化被侵犯的类

    // Realize newly-resolved future classes, in case CF manipulates them
    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);
    }

    ts.log("IMAGE TIMES: realize future classes");

    if (DebugNonFragileIvars) {
        // 实现所有类
        realizeAllClasses();
    }

在以上的分析中,我们需要重点关注 3 中的 readClass(读取类)和 9 中的 realizeClassWithoutSwift(实现类)。下面来展开分析这两个方法

readClass 读取类

前面介绍过,在调用 readClass 前,cls 还只是一个地址,执行该方法后 cls 就变成了类的名字。这个方法中做了怎样的处理呢?我们跳到源码,如下

/***********************************************************************
* 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)
{
    const char *mangledName = cls->mangledName();
    
    // 当前类的父类中若有丢失的weak-linked类,则返回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();

    // 判断是不是后期要处理的类,正常情况下,不会走到popFutureNamedClass,因为这是专门针对未来待处理的类的操作。因此不会对ro、rw进行操作
    Class replacing = nil;
    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;
    }
    
    // 判断是否类是否已经加载到内存
    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);
        // 插入表,即相当于从 mach-O 文件读取到内存中
        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;
}

其中关键代码是 addNamedClassaddClassTableEntry

    1. 通过 mangledName 获取类名,源码实现如下
    const char *mangledName() { 
        // fixme can't assert locks here
        ASSERT(this);

        if (isRealized()  ||  isFuture()) {
            // 如果实现的类或则未实现的未来的类,则从 ro 中获取 name
            return data()->ro()->name;
        } else {
            // 从 mach-O 的数据 data 中获取 name
            return ((const class_ro_t *)data())->name;
        }
    }
    1. 当前类的父类中若有丢失的 weak-linked 类,则返回 nil
    1. 判断是不是后期需要处理的类,正常情况下,不会走到 popFutureNamedClass,这是专门针对未来待处理的类的操作。可以通过断点调试验证
    • data()Mach-o 的数据,并不在 class 内存中
    • rw 的赋值是从 mach-o中的 data强转赋值的
    • rw 里的 ro 是从 ro 复制过去的
    1. 通过 addNamedClass 将当前类添加到已经创建好的 gdb_objc_realized_classes 哈希表,该表用于存放所有类
/***********************************************************************
* 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;
    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());
}
    1. 通过 addClassTableEntry,将初始化的类添加到 allocatedClasses
/***********************************************************************
* 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.insert(cls);
    if (addMeta) // 元类
        addClassTableEntry(cls->ISA(), false);
}

如果我们想在源码中定位自定义的类,可以在获取类名的下面添加自定义判断,如下

const char *customClsName = "LGPerson";
if (strcmp(mangledName, customClsName) == 0) {
    printf("%s --- 这个是我要研究的 --- %s", __func__, mangledName);
}

readClass 的主要作用就是将 Mach-o中的类读取到内存,即插入表中,目前的类仅有两个信息:地址以及名称,其他数据还未展示出来(如 data 数据)

realizeClassWithoutSwift 实现类

realizeClassWithoutSwift 主要作用实现类,将类的 data 数据加载到内存。源码很长,这里主要分析关键代码部分,其主要操作如下

    1. mach-o 中读取 data 数据,并设置 rorw
    1. 递归调用 realizeClassWithoutSwift,确定继承链
    1. 通过 methodizeClass 方法化类
1. data 数据的读取

mach-o 中读取 data 数据,将其强转为 ro,以及 rw 初始化,设置 rw 中的 ro,源码如下

    // fixme verify class is not in an un-dlopened part of the shared cache?
    auto ro = (const class_ro_t *)cls->data(); // 读取类机构的 bits 属性
    auto isMeta = ro->flags & RO_META;
    if (ro->flags & RO_FUTURE) { // 如果是元类
        // This was a future class. rw data is already allocated.
        rw = cls->data(); // rw 赋值
        ro = cls->data()->ro(); // ro 赋值
        ASSERT(!isMeta);
        cls->changeInfo(RW_REALIZED|RW_REALIZING, RW_FUTURE);
    } else {
        // Normal class. Allocate writeable class data.
        rw = objc::zalloc<class_rw_t>(); //申请开辟 rw 空间
        rw->set_ro(ro); // 设置 rw 中的 ro
        rw->flags = RW_REALIZED|RW_REALIZING|isMeta;
        cls->setData(rw); // 将 cls 的 data 赋值为 rw 
    }
  • ro: (readOnly 只读),在编译时已经确定了内存,包含类名、方法、协议、实例变量等信息。它是一片干净的内存(cleanMemory),即加载后不会再发生改变

  • rw: (readWrite 可读可写),由于 iOS 运行时,可能会不断的内存增删改查,防止对原始数据更改,将原始数据(ro)拷贝一份到 rw;但是并不是每个类都会进行动态的插入(可能有几万个类或几十万个类,被修改的就那么多,极少数),所以就有了 rwe

  • rwe(dirty memory 胀内存),只要动态处理了,才会生成对应的 rwe。一般情况下,rw 是从 ro 里面读取的,如果有运行时,则从 rwe 中读取

    const class_ro_t *ro() const {
        auto v = get_ro_or_rwe();
        if (slowpath(v.is<class_rw_ext_t *>())) {
            return v.get<class_rw_ext_t *>()->ro;
        }
        return v.get<const class_ro_t *>();
    }
2. 递归循环,确定继承链

主要是通过递归循环确定继承链(父类、元类)

    // Realize superclass and metaclass, if they aren't already.
    // This needs to be done after RW_REALIZED is set above, for root classes.
    // This needs to be done after class index is chosen, for root metaclasses.
    // This assumes that none of those classes have Swift contents,
    //   or that Swift's initializers have already been called.
    //   fixme that assumption will be wrong if we add support
    //   for ObjC subclasses of Swift classes.
    // 确定继承链
    supercls = realizeClassWithoutSwift(remapClass(cls->superclass), nil);
    metacls = realizeClassWithoutSwift(remapClass(cls->ISA()), nil);

#if SUPPORT_NONPOINTER_ISA
    if (isMeta) {
        // Metaclasses do not need any features from non pointer ISA
        // This allows for a faspath for classes in objc_retain/objc_release.
        cls->setInstancesRequireRawIsa();
    } else {
        // Disable non-pointer isa for some classes and/or platforms.
        // Set instancesRequireRawIsa.
        bool instancesRequireRawIsa = cls->instancesRequireRawIsa();
        bool rawIsaIsInherited = false;
        static bool hackedDispatch = false;

        if (DisableNonpointerIsa) {
            // Non-pointer isa disabled by environment or app SDK version
            instancesRequireRawIsa = true;
        }
        else if (!hackedDispatch  &&  0 == strcmp(ro->name, "OS_object"))
        {
            // hack for libdispatch et al - isa also acts as vtable pointer
            hackedDispatch = true;
            instancesRequireRawIsa = true;
        }
        else if (supercls  &&  supercls->superclass  &&
                 supercls->instancesRequireRawIsa())
        {
            // This is also propagated by addSubclass()
            // but nonpointer isa setup needs it earlier.
            // Special case: instancesRequireRawIsa does not propagate
            // from root class to root metaclass
            instancesRequireRawIsa = true;
            rawIsaIsInherited = true;
        }

        if (instancesRequireRawIsa) {
            cls->setInstancesRequireRawIsaRecursively(rawIsaIsInherited);
        }
    }
// SUPPORT_NONPOINTER_ISA
#endif

    // Update superclass and metaclass in case of remapping
    // 更新 cls 的父类以及元类的映射
    cls->superclass = supercls;
    cls->initClassIsa(metacls);

    // Reconcile instance variable offsets / layout.
    // This may reallocate class_ro_t, updating our ro variable.
    if (supercls  &&  !isMeta) reconcileInstanceVariables(cls, supercls, ro);

    // Set fastInstanceSize if it wasn't set already.
    cls->setInstanceSize(ro->instanceSize);

    // Copy some flags from ro to rw
    if (ro->flags & RO_HAS_CXX_STRUCTORS) {
        cls->setHasCxxDtor();
        if (! (ro->flags & RO_HAS_CXX_DTOR_ONLY)) {
            cls->setHasCxxCtor();
        }
    }
    
    // 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);
    }
3. methodizeClass 方法化类

ro 中读取方法列表(包括分类中的方法)、属性列表、协议列表赋值给 rw,并返回 cls,源码如下

/***********************************************************************
* methodizeClass
* Fixes up cls's method list, protocol list, and property list.
* Attaches any outstanding categories.
* Locking: runtimeLock must be held by the caller
**********************************************************************/
static void methodizeClass(Class cls, Class previously)
{
    runtimeLock.assertLocked();

    bool isMeta = cls->isMetaClass();
    auto rw = cls->data();
    auto ro = rw->ro();
    auto rwe = rw->ext();
    
    const char *mangledName  = cls->mangledName();

    // 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.
    // 获取 ro 中的方法列表
    method_list_t *list = ro->baseMethods();
    if (list) {
        // 排序
        prepareMethodLists(cls, &list, 1, YES, isBundleClass(cls));
        // 如果是运行时,将方法列表拷贝到 rwe 中
        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.
    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
}
  • 方法列表排序

通过 ro 获取 baseMethods 方法列表,然后再进行 prepareMethodLists 方法排序,如果是运行时,将排序好的方法列表插入 rwe 中,查看方法排序的源码如下

static void 
prepareMethodLists(Class cls, method_list_t **addedLists, int addedCount,
                   bool baseMethods, bool methodsFromBundle)
{
    ...

    // Add method lists to array.
    // Reallocate un-fixed method lists.
    // The new methods are PREPENDED to the method list array.

    for (int i = 0; i < addedCount; i++) {
        method_list_t *mlist = addedLists[I];
        ASSERT(mlist);

        // Fixup selectors if necessary
        if (!mlist->isFixedUp()) {
            fixupMethodList(mlist, methodsFromBundle, true/*sort*/);
        }
    }
    ...
}

进入 fixupMethodList 源码实现,如下

static void 
fixupMethodList(method_list_t *mlist, bool bundleCopy, bool sort)
{
    runtimeLock.assertLocked();
    ASSERT(!mlist->isFixedUp());

    // fixme lock less in attachMethodLists ?
    // dyld3 may have already uniqued, but not sorted, the list
    if (!mlist->isUniqued()) {
        mutex_locker_t lock(selLock);
    
        // Unique selectors in list.
        for (auto& meth : *mlist) {
            const char *name = sel_cname(meth.name);
            meth.name = sel_registerNameNoLock(name, bundleCopy);
        }
    }

    // Sort by selector address.
    if (sort) {
        // 根据sel地址排序
        method_t::SortBySELAddress sorter;
        std::stable_sort(mlist->begin(), mlist->end(), sorter);
    }
    
    // Mark method list as uniqued and sorted
    mlist->setFixedUp();
}

可以看到方法的排序是按照 selector address 排序的

验证方法排序

  • methodizeClass 方法中添加自定义逻辑(目的是断到我们要研究的类,排除其他类和元类的干扰),并添加断点,运行 objc-781 源码
  • 来到断点,打印当前方法列表
  • 进入 prepareMethodLists 开始方法排序流程
  • 进入 fixupMethodList 源码,排序
  • 来到排序后的断点,打印方法列表

由以上排序方法前后对比,可以验证 methodizeClass 实现了类中 方法 的序列化

attachToClass

主要是将分类添加到主类中,其源码实现如下

    void attachToClass(Class cls, Class previously, int flags)
    {
        runtimeLock.assertLocked();
        ASSERT((flags & ATTACH_CLASS) ||
               (flags & ATTACH_METACLASS) ||
               (flags & ATTACH_CLASS_AND_METACLASS));

        auto &map = get();
        // 找到一个分类进来一次,即一个个加载分类
        auto it = map.find(previously);

        // 当主类没有实现 load,分类开始加载,迫使主类加载,会走到 if 流程里面
        if (it != map.end()) {
            category_list &list = it->second;
            // 判断是否是元类
            if (flags & ATTACH_CLASS_AND_METACLASS) {
                int otherFlags = flags & ~ATTACH_CLASS_AND_METACLASS;
                // 实例方法
                attachCategories(cls, list.array(), list.count(), otherFlags | ATTACH_CLASS);
                // 类方法
                attachCategories(cls->ISA(), list.array(), list.count(), otherFlags | ATTACH_METACLASS);
            } else {
                // 如果不是元类,则只走一次 attachCategories
                attachCategories(cls, list.array(), list.count(), flags);
            }
            map.erase(it);
        }
    }

attachToClass 中的外部循环是找到一个分类就会进到 attachCategories 一次,即找一个就循环一次

attachCategories

主要是准备分类的数据,源码如下

// Attach method lists and properties and protocols from categories to a class.
// Assumes the categories in cats are all loaded and sorted by load order, 
// oldest categories first.
static void
attachCategories(Class cls, const locstamped_category_t *cats_list, uint32_t cats_count,
                 int flags)
{
    if (slowpath(PrintReplacedMethods)) {
        printReplacements(cls, cats_list, cats_count);
    }
    if (slowpath(PrintConnecting)) {
        _objc_inform("CLASS: attaching %d categories to%s class '%s'%s",
                     cats_count, (flags & ATTACH_EXISTING) ? " existing" : "",
                     cls->nameForLogging(), (flags & ATTACH_METACLASS) ? " (meta)" : "");
    }

    /*
     * Only a few classes have more than 64 categories during launch.
     * This uses a little stack, and avoids malloc.
     *
     * Categories must be added in the proper order, which is back
     * to front. To do that with the chunking, we iterate cats_list
     * from front to back, build up the local buffers backwards,
     * and call attachLists on the chunks. attachLists prepends the
     * lists, so the final result is in the expected order.
     */
    constexpr uint32_t ATTACH_BUFSIZ = 64;
    method_list_t   *mlists[ATTACH_BUFSIZ];
    property_list_t *proplists[ATTACH_BUFSIZ];
    protocol_list_t *protolists[ATTACH_BUFSIZ];

    uint32_t mcount = 0;
    uint32_t propcount = 0;
    uint32_t protocount = 0;
    bool fromBundle = NO;
    bool isMeta = (flags & ATTACH_METACLASS);
    
    // 往`本类` rwe 中`添加属性、方法、协议`等
    auto rwe = cls->data()->extAllocIfNeeded();

    for (uint32_t i = 0; i < cats_count; i++) {
        auto& entry = cats_list[I];

        method_list_t *mlist = entry.cat->methodsForMeta(isMeta);
        if (mlist) {
            if (mcount == ATTACH_BUFSIZ) {
                prepareMethodLists(cls, mlists, mcount, NO, fromBundle);
                rwe->methods.attachLists(mlists, mcount);
                mcount = 0;
            }
            mlists[ATTACH_BUFSIZ - ++mcount] = mlist;
            fromBundle |= entry.hi->isBundle();
        }

        property_list_t *proplist =
            entry.cat->propertiesForMeta(isMeta, entry.hi);
        if (proplist) {
            if (propcount == ATTACH_BUFSIZ) {
                rwe->properties.attachLists(proplists, propcount);
                propcount = 0;
            }
            proplists[ATTACH_BUFSIZ - ++propcount] = proplist;
        }

        protocol_list_t *protolist = entry.cat->protocolsForMeta(isMeta);
        if (protolist) {
            if (protocount == ATTACH_BUFSIZ) {
                rwe->protocols.attachLists(protolists, protocount);
                protocount = 0;
            }
            protolists[ATTACH_BUFSIZ - ++protocount] = protolist;
        }
    }

    // 如果存在分类
    if (mcount > 0) {
        prepareMethodLists(cls, mlists + ATTACH_BUFSIZ - mcount, mcount, NO, fromBundle);
        rwe->methods.attachLists(mlists + ATTACH_BUFSIZ - mcount, mcount);
        if (flags & ATTACH_EXISTING) flushCaches(cls);
    }

    rwe->properties.attachLists(proplists + ATTACH_BUFSIZ - propcount, propcount);

    rwe->protocols.attachLists(protolists + ATTACH_BUFSIZ - protocount, protocount);
}
    1. 在往本类中添加属性、方法、协议时,首先获取 rwe(因为要对原来的内存进行更改)。extAllocIfNeeded 的源码如下
    class_rw_ext_t *extAllocIfNeeded() {
        auto v = get_ro_or_rwe();
        if (fastpath(v.is<class_rw_ext_t *>())) {
            return v.get<class_rw_ext_t *>();
        } else {
            return extAlloc(v.get<const class_ro_t *>());
        }
    }

判断rwe是否存在,如果存在则直接获取,如果不存在则开辟。extAlloc 的源码如下

class_rw_ext_t *
class_rw_t::extAlloc(const class_ro_t *ro, bool deepCopy)
{
    runtimeLock.assertLocked();

    // 创建 rwe
    auto rwe = objc::zalloc<class_rw_ext_t>();

    rwe->version = (ro->flags & RO_META) ? 7 : 0;

    method_list_t *list = ro->baseMethods();
    if (list) {
        if (deepCopy) list = list->duplicate();
        rwe->methods.attachLists(&list, 1);
    }

    // See comments in objc_duplicateClass
    // property lists and protocol lists historically
    // have not been deep-copied
    //
    // This is probably wrong and ought to be fixed some day
    property_list_t *proplist = ro->baseProperties;
    if (proplist) {
        rwe->properties.attachLists(&proplist, 1);
    }

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

    set_ro_or_rwe(rwe, ro);
    return rwe;
}
    1. 分类存在时方法处理,这里 mlists 是一个二维数组
    • mlists[ATTACH_BUFSIZ - ++mcount] = mlist; 可以看到将分类中的方法倒序插入 mlists

    • 分类方法个数大于 0 调用 rwe->methods.attachLists(mlists + ATTACH_BUFSIZ - mcount, mcount); 存入 mlists 的末尾

在 for 循环中我们看到有对分类个数判断,ATTACH_BUFSIZ 的值为 64,即最多允许 64 个分类

attachLists 方法插入

进入 attachLists 方法的源码实现

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

        if (hasArray()) {
            // many lists -> many lists
            // 计算数组中旧 lists 的大小
            uint32_t oldCount = array()->count;
            // 计算新的容量大小
            uint32_t newCount = oldCount + addedCount;
            // 根据新的容量大小,开辟一个数组,类型是 array_t,通过array()获取
            setArray((array_t *)realloc(array(), array_t::byteSize(newCount)));
            // 设置新数组的大小
            array()->count = newCount;
            // 将旧数据(lists)从数组的下标 addedCount 开始存放,大小为旧数据大小 * 单个旧list大小
            memmove(array()->lists + addedCount, array()->lists, 
                    oldCount * sizeof(array()->lists[0]));
            // 将要添加的数据(addedLists)从数组的下标 0 开始存放,大小为新数据大小 * 单个list大小
            memcpy(array()->lists, addedLists, 
                   addedCount * sizeof(array()->lists[0]));
        }
        else if (!list  &&  addedCount == 1) {
            // 0 lists -> 1 list
            // list 不存在且 addedCount 个数为 1,此时的 list 是一个一维数组
            list = addedLists[0];
        } 
        else {
            // 1 list -> many lists
            // 获取旧的 list
            List* oldList = list;
            uint32_t oldCount = oldList ? 1 : 0;
            uint32_t newCount = oldCount + addedCount;
            // 开辟一个 newCount 容量大小的集合,类型是 array_t,即创建一个数组,放到 array 中,通过 array() 获取
            setArray((array_t *)malloc(array_t::byteSize(newCount)));
            // 设置数组的大小
            array()->count = newCount;
            // 判断 old 是否存在,将旧的 list 放入到数组的末尾
            if (oldList) array()->lists[addedCount] = oldList;
            // 其中 array()->lists 表示首位元素位置
            memcpy(array()->lists, addedLists, 
                   addedCount * sizeof(array()->lists[0]));
        }
    }

从源码中可以看出,插入表主要分为三种情况:

  • 多对多:当前 list_array_tt 二维数组中有多个一维数组

    • 计算数组中 旧 lists 的大小
    • 计算新的容量大小(旧数据大小+要添加的数据大小)
    • 开辟一个数组,类型是 array_t,大小为新的容量大小
    • 设置数组大小
    • 旧数据的内存平移(通过 memmove 后移)
    • 新数据的存储(通过 memcpy 存储在数组前面)
  • 0 对 1:当前 list_array_tt 二维数组为空且新增大小数目为 1

    • 直接赋值 addedList 的第一个元素
  • 一对多:当前 list_array_tt 二维数组中有一个一维数组

    • 获取旧的 list
    • 计算新的容量 = 旧list个数 + 新lists的个数
    • 开辟一个数组,类型是 array_t,大小为新的容量大小
    • 设置数组的大小
    • 判断 old 是否存在,将 旧的list 放入到数组的末尾
    • 从数组起始位置开始存入 新的list,其中 array()->lists 表示首位元素位置

对于开发中子类实现父类同名方法、分类同名方法覆盖本类方法,这里也就可以解释了

memmove 内存平移,memcpy(开始位置,放什么,放多大)
memcpy 从原内存地址的起始位置开始拷贝若干个字节到目标内存地址中,速度快

到此,map_images 的加载流程基本上结束了,但是其中的一些细节(如 rwe 的数据加载、分类数据的加载、懒加载与非懒加载的调用流程等)会在后面的文章中详细解析

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,839评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,543评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,116评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,371评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,384评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,111评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,416评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,053评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,558评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,007评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,117评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,756评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,324评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,315评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,539评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,578评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,877评论 2 345

推荐阅读更多精彩内容