iOS-底层原理 12 类的加载

637

从上一节启动时dyld和objc的关联 可以看出,dyld_init在初始化程序时(initializeMainExecutable方法)最终会进入到_objc_init方法,_objc_init执行完回到dyld_init方法并执行主程序。

其中_objc_init主要执行类的加载(类信息从ro到rw的装载的过程)。_dyld_objc_notify_register是类的注册方法,通知 dyld 在适当的时机调用 map_imagesload_imagesunmap_image 函数。map_images方法中有class、selector、protocol、category 等的加载和处理。

懒加载类和非懒加载类

类分为懒加载类和非懒加载类,他们的加载时机不同。

懒加载类:在执行第一个消息的时候才会加载的类
非懒加载类(本文主要研究):在程序启动时即执行了加载(内部实现了+load方法)

以下是懒加载类和非懒加载类执行的函数方法顺序。

截屏2021-08-05 上午10.33.05.png

如果想深入研究类的加载过程可以查看如下内容

map_images

map_images 的工作是:处理由 dyld 映射的给定镜像。负责管理文件和动态库中所有的符号(class、selector、protocol、category 等)

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 ,(这里只关注读取镜像的逻辑,所以做了一些源码的省略处理。)

void 
map_images_nolock(unsigned mhCount, const char * const mhPaths[],
                  const struct mach_header * const mhdrs[])
{
    ......

    // 从objc的元数据查找所有的镜像
    if (hCount > 0) {
        _read_images(hList, hCount, totalClasses, unoptimizedTotalClasses);
    }
    for (auto func : loadImageFuncs) {
        for (uint32_t i = 0; i < mhCount; i++) {
            func(mhdrs[I]);
        }
    }
}

read_images

从主程序的 【machO】 中找到所有的镜像文件,调用 _read_images ,执行所有的类注册和修复等功能。在所有设置完成后调用镜像加载。那么在加载镜像之前的操作就聚集在了 _read_images 里面了,这也是我们重点研究的对象。源码很长,精简如下:

void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int unoptimizedTotalClasses)
{
    ......

    // ① 控制进行只做一次加载(建表:未在dyld共享缓存中的已命名类的列表,无论是否实现)
    if (!doneOnce) {
        doneOnce = YES;
        int namedClassesSize = 
            (isPreoptimized() ? unoptimizedTotalClasses : totalClasses) * 4 / 3;
        gdb_objc_realized_classes =
            NXCreateMapTable(NXStrValueMapPrototype, namedClassesSize);
    }
    ......
    // ② 修复@selector 引用(修复预编译阶段的 `@selector` 的混乱问题)
    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);
            UnfixedSelectors += count;
            for (i = 0; i < count; i++) {
                const char *name = sel_cname(sels[i]);
                SEL sel = sel_registerNameNoLock(name, isBundle);
                if (sels[i] != sel) {
                    sels[i] = sel;
                }
            }
        }
    }
    // ③ 修复未解析的 future class, 标记 Bundle class
    for (EACH_HEADER) {
        classref_t const *classlist = _getObjc2ClassList(hi, &count);
        for (i = 0; i < count; i++) {
            Class cls = (Class)classlist[I];
            Class newCls = readClass(cls, headerIsBundle, headerIsPreoptimized);

            if (newCls != cls  &&  newCls) {
                resolvedFutureClasses = (Class *)
                    realloc(resolvedFutureClasses, 
                            (resolvedFutureClassCount+1) * sizeof(Class));
                resolvedFutureClasses[resolvedFutureClassCount++] = newCls;
            }
        }
    }
    
    // ④ 修复重映射的类;(一些没有被镜像文件加载进来的类) 
    if (!noClassesRemapped()) {
        for (EACH_HEADER) {
            Class *classrefs = _getObjc2ClassRefs(hi, &count);
            for (i = 0; i < count; i++) {
                remapClassRef(&classrefs[I]);
            }
            classrefs = _getObjc2SuperRefs(hi, &count);
            for (i = 0; i < count; i++) {
                remapClassRef(&classrefs[I]);
            }
        }
    }
#if SUPPORT_FIXUP

    // ⑤ Fix up old objc_msgSend_fixup call sites  

    for (EACH_HEADER) {
        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);
        }
    }
#endif

    // ⑥ readProtocol

    NXMapTable *protocol_map = protocols();
    protocol_t * const *protolist = _getObjc2ProtocolList(hi, &count);
    for (EACH_HEADER) {
        for (i = 0; i < count; i++) {
            readProtocol(protolist[i], cls, protocol_map, 
                         isPreoptimized, isBundle);
        }
    }

    
    // ⑦ Fix up @protocol references

    for (EACH_HEADER) {
        if (launchTime && cacheSupportsProtocolRoots && hi->isPreoptimized())
            continue;
        protocol_t **protolist = _getObjc2ProtocolRefs(hi, &count);
        for (i = 0; i < count; i++) {
            remapProtocolRef(&protolist[I]);
        }
    }
    
    // ⑧ load_categories_nolock
    if (didInitialAttachCategories) {
        for (EACH_HEADER) {
            load_categories_nolock(hi);
        }
    }

   // ⑨ non-lazy 非懒加载类的加载

    for (EACH_HEADER) {
        classref_t const *classlist = 
            _getObjc2NonlazyClassList(hi, &count);
        for (i = 0; i < count; i++) {
            Class cls = remapClass(classlist[i]);
            if (!cls) continue;

            addClassTableEntry(cls);
            realizeClassWithoutSwift(cls, nil);
        }
    }

    //  ⑩ realize future classes (没有被处理的类,)
    // Realize newly-resolved future classes, in case CF manipulates them  实现新解析的未来类,以防CF操作它们
    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);
    }

    if (DebugNonFragileIvars) {
        realizeAllClasses();
    }
}

简化总结如下:

  • ①:条件控制只做一次:

建表gdb_objc_realized_classes ,存储未在dyld共享缓存中的已命名类的列表,无论是否实现

  • ②:修复@selector 引用:

修复预编译阶段 @selector 的混乱问题,我们知道,SEL 是一个带地址的字符串,尽管两个方法的名字相同,但方法的地址未必相同。可能多个框架都会有init方法,在系统中读取方法的地址是按照框架在主程中的偏移以及方法在框架内的偏移来定位的。因此,我们需要对@selector进行适当调整。

  • ③:修复未解析的 future classes.

从【MachO】__objc_classlist section中加载类列表,遍历列表中的类,进行readClass,如果readClass的结果与列表中的类不同,则进行修复操作,但这一般不会出现,只有类被移动并且没有被删除才会出现。在readClass中,从cls->mangledName()中取到类的名字,将类的名字与地址关联,插入到第①步创建的gdb_objc_realized_classes中,同时将该类以及元类插入到allocatedClasses 表。 在这一步中,readClass 将类的地址和名字 加载到了内存中。

  • ④:修复重映射的类:

如果存在没有被镜像文件加载进来的类,则在此时进行重映射。

  • ⑤:修复一些旧的 objc_msgSend_fixup 调用

主要对一些旧的消息修复进行强制修复工作,如alloc -> objc_alloc、allocWithZone -> objc_allocWithZone 等。

  • ⑥:readProtocol:

创建存储proctol的哈希表,从【MachO】__objc_protolist section中读取协议列表,遍历协议列表,进行readProtocol,将协议添加到proctol表,注册到内存。

  • ⑦: Fix up @protocol references

预先优化的镜像可能已经有了正确的protocol引用,但是我们并不能确定,所以这里进行一次修复工作,以防被引用的协议被重新分配。

  • ⑧:load_categories

加载分类,需要注意的是,这里并不会加载分类,只有在didInitialAttachCategories赋值为true之后才会执行,而didInitialAttachCategories赋值为true的过程是在_dyld_objc_notify_register的调用完成后的第一个load_images调用时赋值的。

  • ⑨:non-lazy 类的加载

这里完成的是非懒加载的类的实现,也就是实现了+load方法的类。 从【MachO】__objc_nlclslistsection中读取非懒加载类列表,执行addClassTableEntry(cls); 将非懒加载类插入到类表,加载到内存。在第③步中,我们加载到内存的类已有了地址和名字,最后执行 realizeClassWithoutSwift对类的结构进行完善。

  • ⑩:realize future classes

如果存在被处理的 future class ,则需要在这里实现,以防CF操作它们。这里的实现也是通过 realizeClassWithoutSwift

_read_images 大致做了上述这些事情,如果只关注类的加载过程的话,可以作如下简化理解

  • 建表gdb_objc_realized_classes。创建一个不在共享缓存的已命名类的表,不关乎这个类是否实现。

  • 从【MachO】__objc_classlist section 中读取类的列表数据获取类的地址信息,将类的名字写入,插入gdb_objc_realized_classes表,并且插入allocatedClasses表。自此,类被载入内存,并且有了地址与名字

  • 实现类的细节(rorw等处理),保证类的结构的完整性。

具体的readClassrealizeClassWithoutSwift 代码分析如下:

readClass

  • cls->mangledName()获取类的名字
  • 如果继承链中存在父类缺失或者weak-linked情况,直接忽略这个类(这个类是不完整的), return nil;
  • 如果这个类是一个早先分配的作为将来要处理的类,那么将objc_class数据复制到future class,保存future的rw数据块;
  • 如果是预优化的类 并且 不是future class, 则 ASSERT;否则执行addNamedClass关联类的地址与名字 ,插入gdb_objc_realized_classes表. 执行addClassTableEntry 插入 allocatedClasses表;
Class readClass(Class cls, bool headerIsBundle, bool headerIsPreoptimized)
{
    const char *mangledName = cls->mangledName();
    // 如果继承链中存在父类缺失或者weak-linked情况,直接忽略这个类, return nil
    if (missingWeakSuperclass(cls)) {
      
        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;
    // 如果这个类是一个早先分配的作为将来要处理的类,那么将objc_class数据复制到future class,保存future的rw数据块
    if (Class newCls = popFutureNamedClass(mangledName)) {
        
        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;
    }
    // 如果是预优化的类 并且 不是future class, 则 ASSERT,
    if (headerIsPreoptimized  &&  !replacing) {
        ASSERT(getClassExceptSomeSwift(mangledName));
    } else {
        // 关联地址与名字 加入gdb_objc_realized_classes表
        addNamedClass(cls, mangledName, replacing);
        // 加入allocatedClasses表
        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;
}

判断gdb_objc_realized_classes表中是否已经存在该名称的类,如果存在,插入nonMetaClasses表。如果不存在插入gdb_objc_realized_classes

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

如果该类是在运行时已知的类(如位于共享缓存中,在一个已加载镜像的数据段中,或者已经用obj_allocateClassPair分配),则不需要插入表,否则插入allocatedClasses表,同时将类的元类也插入表中。allocatedClasses表是在runtime_init()时创建的.

static void
addClassTableEntry(Class cls, bool addMeta = true)
{
    runtimeLock.assertLocked();

    auto &set = objc::allocatedClasses.get();

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

    if (!isKnownClass(cls))
        set.insert(cls);
    if (addMeta)
        addClassTableEntry(cls->ISA(), false);
}

至此,【MachO】中的类已载入内存,并且有了名字和地址,但是数据还没有关联,而数据的关联是在realizeClassWithoutSwift 中进行的。

realizeClassWithoutSwift

  • cls->data()中读取类的信息,如果是 future class ,对rwro赋值;如果是正常的类,则开辟rw空间,对ro进行赋值,并拷贝到rw中。
  • 递归实现类的父类以及元类,随后更新该类的父类和元类以备重新映射。确保继承链以及isa链的完整性。
  • ro中赋值一些到rw中。
  • 如果父类存在,则将这个类链接到它的父类的子类列表,否则作为一个新的根类。
  • 执行methodizeClass,设置类的属性列表,方法列表,协议列表,添加分类等。
static Class realizeClassWithoutSwift(Class cls, Class previously)
{
    runtimeLock.assertLocked();

    class_rw_t *rw;
    Class supercls;
    Class metacls;

    if (!cls) return nil;
    if (cls->isRealized()) return cls;    // 如果类已实现,则直接返回
    ASSERT(cls == remapClass(cls));


    auto ro = (const class_ro_t *)cls->data();   // 从data()中取类的信息
    auto isMeta = ro->flags & RO_META;
    if (ro->flags & RO_FUTURE) {
        // This was a future class. rw data is already allocated.
        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 = objc::zalloc<class_rw_t>();
        rw->set_ro(ro);//设置rw
        rw->flags = RW_REALIZED|RW_REALIZING|isMeta;
        cls->setData(rw);
    }

#if FAST_CACHE_META
    if (isMeta) cls->cache.setBit(FAST_CACHE_META);
#endif

 
    cls->chooseClassArrayIndex();

    if (PrintConnecting) {
        _objc_inform("CLASS: realizing class '%s'%s %p %p #%u %s%s",
                     cls->nameForLogging(), isMeta ? " (meta)" : "", 
                     (void*)cls, ro, cls->classArrayIndex(),
                     cls->isSwiftStable() ? "(swift)" : "",
                     cls->isSwiftLegacy() ? "(pre-stable swift)" : "");
    }
    // 实现超类和元类,如果它们还没有实现的话。
    // 对于根类,这需要在上面设置rw_realize之后进行。
    // 对于根元类,这需要在选择类索引之后完成。
    supercls = realizeClassWithoutSwift(remapClass(cls->superclass), nil);
    metacls = realizeClassWithoutSwift(remapClass(cls->ISA()), nil);

#if SUPPORT_NONPOINTER_ISA
    ...... NONPOINTER_ISA的一些处理
#endif

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

    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();
        }
    } 
    // 从ro或超类传播关联对象禁止标志。
    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,添加属性列表,协议列表,方法列表等。
    methodizeClass(cls, previously);

    return cls;
}

load_images

当镜像文件映射完毕后,会继而执行 load_images ,处理在dyld中已映射完毕的镜像的 +load 方法.

void load_images(const char *path __unused, const struct mach_header *mh)
{
    if (!didInitialAttachCategories && didCallDyldNotifyRegister) {
        didInitialAttachCategories = true;
        loadAllCategories();
    }

    // Return without taking locks if there are no +load methods here.
    if (!hasLoadMethods((const headerType *)mh)) return;
    recursive_mutex_locker_t lock(loadMethodLock);

    // Discover load methods
    {
        mutex_locker_t lock2(runtimeLock);
        prepare_load_methods((const headerType *)mh);
    }
    
    //调用load方法
    // Call +load methods (without runtimeLock - re-entrant)
    call_load_methods();
}

首先,如果 libobjc.A.dylib 动态库的镜像文件映射完毕且首次执行load_images的时候,会调用loadAllCategories() 函数,完成所有分类的加载。

系统依赖的动态库的很多,每一个动态库在执行load_images 函数的时候都会判断当前动态库的【MachO】中__objc_nlclslist__objc_nlcatlist 列表是否有内容。没有直接return。如果有,执行prepare_load_methods

prepare_load_methods

  • 从当前动态库的【MachO】header中读取__objc_nlclslist,执行 schedule_class_load;
  • 从当前动态库的【MachO】header中读取__objc_nlcatlist,随后迫使主类实现,继而执行add_category_to_loadable_list ,将待执行+load的分类添加loadable_categories表中。
void prepare_load_methods(const headerType *mhdr)
{
    size_t count, I;
    runtimeLock.assertLocked();
    // 类的load
    classref_t const *classlist = 
        _getObjc2NonlazyClassList(mhdr, &count);  
    for (i = 0; i < count; i++) {  
        schedule_class_load(remapClass(classlist[i]));
    }
    // 分类的load
    category_t * const *categorylist = _getObjc2NonlazyCategoryList(mhdr, &count);
    for (i = 0; i < count; i++) {
        category_t *cat = categorylist[I];
        Class cls = remapClass(cat->cls);
        if (!cls) continue;  // category for ignored weak-linked class
        if (cls->isSwiftStable()) {
            _objc_fatal("Swift class extensions and categories on Swift "
                        "classes are not allowed to have +load methods");
        }
        realizeClassWithoutSwift(cls, nil);
        ASSERT(cls->ISA()->isRealized());
        add_category_to_loadable_list(cat);
    }
}

 call_load_methods()

prepare_load_methods 函数,将类,父类,分类,添加到对应的待执行表中,接下来就要执行 call_load_methods()执行表中类的+load方法。

void call_load_methods(void)
{
    static bool loading = NO;
    bool more_categories;
    loadMethodLock.assertLocked();
    // Re-entrant calls do nothing; the outermost call will finish the job.
    if (loading) return;
    loading = YES;
    
    void *pool = objc_autoreleasePoolPush();
    do {
        // 重复调用load 直到没有为止
        // 1. Repeatedly call class +loads until there aren't any more
        while (loadable_classes_used > 0) {
            call_class_loads();
        }
        // 调用分类的load
        // 2. Call category +loads ONCE
        more_categories = call_category_loads();
        
        // 3. Run more +loads if there are classes OR more untried categories
    } while (loadable_classes_used > 0  ||  more_categories);
    
    objc_autoreleasePoolPop(pool);
    
    loading = NO;
}
  • 进入do....while 循环
  • 从先前存入的表数据loadable_classes中取出可执行+load方法的类数据,进行消息发送,执行+load方法,
  • loadable_categories 表中取出可执行+load方法的分类数据,进行消息发送,执行+load方法。

总结

本篇文章主要是从_objc_init开始,结合map_images load_images 来探究类的加载过程。类的加载其实是rw、ro装载的过程,在内存中将数据拿出来,通过类型的强制转换,再使用rw->set_ro(ro),将ro装载到rw内,然后使用cls->setData(rw)将rw装载进class内,这样类的加载有完美的完成了

参考

iOS 类的加载

iOS 类的加载 上