iOS 类的加载

iOS 应用程序加载 一篇,我们得知,app由内核引导启动,之后交由dyld 主导,完成运行环境的初始化,配合ImageLoader将二进制文件按格式加载到内存,动态链接依赖库,并由runtime负责加载成objc定义的结构,所有初始化工作结束之后,dyld调用应用程序的main函数。

其中,dyldobjc 互相配合,dyld 加载动态库的过程中初始化 objcobjc在初始化的过程中注册回调函数 _dyld_objc_notify_register,通知dyld执行 map_imagesload_imagesunmap_images 以完成映射、载入等过程(详细过程参见 iOS 应用程序加载 )。

这一设计是很有必要的,一来分工明确,各司其职;二来runtime接管了这部分工作,可以实现runtime的动态性。接下来我们结合 libobjc 的初始化来探究 iOS 类的加载过程。

0 . 从 _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();     // 处理线程key的绑定
    static_init();  // 运行C++静态构造函数
    runtime_init(); // runtime运行时环境初始化,里面主要是:unattachedCategories,allocatedClasses

    exception_init(); // 初始化libobjc的异常处理系统
    cache_init(); // 缓存条件初始化
    _imp_implementationWithBlock_init();

    _dyld_objc_notify_register(&map_images, load_images, unmap_image);

#if __OBJC2__
    didCallDyldNotifyRegister = true;
#endif
}

在任何 image 初始化之前,要先确保 libSystem_initializer 的正常执行,它是优先级最高的,必须先保证系统库的正常初始化,之后 经由 libdispatch_init --> _os_object_init 继而调用 _objc_init

在 _objc_init 中:

  • environ_init()
    读取影响运行时的环境变量.
  • tls_init()
    处理线程key的绑定,处理每个线程数据的析构函数.
  • static_init()
    运行C ++静态构造函数,在dyld调用我们的静态构造函数之前,libc 会调用 _objc_init(), 因此我们必须自己做.
  • runtime_init()
    runtime运行时环境初始化,里面主要是:unattachedCategories(尚未附加到本类的分类表),allocatedClasses(用objc_allocateClassPair分配的所有类(和元类)的表) 两个表的创建.
  • exception_init()
    初始化libobjc的异常处理系统以监测异常.
  • cache_init()
    缓存条件初始化
  • _imp_implementationWithBlock_init()
    启动 trampoline machinery。通常这不会做什么,因为所有的初始化都是惰性的,但是对于某些进程,我们会迫不及待地加载trampolines dylib。

_objc_init 完成上述的 init 工作之后,通知 dyld 在适当的时机调用 map_images, load_images, unmap_image 函数。

1. 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]);
        }
    }
}

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

1.1 _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 大致做了上述这些事情,如果只关注类的加载过程的话,可以作如下简化理解:

  • 1.建表gdb_objc_realized_classes
    创建一个不在共享缓存的已命名类的表,不关乎这个类是否实现。
  • 2.readClass
    从【MachO】__objc_classlist section 中读取 类的列表数据获取类的地址信息,将类的名字写入,插入gdb_objc_realized_classes表,并且插入allocatedClasses表。自此,类被载入内存,并且有了地址与名字
  • 3.realizeClassWithoutSwift
    实现类的细节(rorw等处理),保证类的结构的完整性。

具体的readClassrealizeClassWithoutSwift代码分析如下:

1.1.1 readClass

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

readClass 处理:

  • 1.从cls->mangledName()获取类的名字
  • 2.如果继承链中存在父类缺失或者weak-linked情况,直接忽略这个类(这个类是不完整的), return nil;
  • 3.如果这个类是一个早先分配的作为将来要处理的类,那么将objc_class数据复制到future class,保存future的rw数据块;
  • 4.如果是预优化的类 并且 不是future class, 则 ASSERT;否则
    执行addNamedClass关联类的地址与名字 ,插入gdb_objc_realized_classes表.
    执行addClassTableEntry 插入 allocatedClasses表;

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

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

1.1.1.2 addClassTableEntry

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

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

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

1.1.2 realizeClassWithoutSwift

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

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

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

realizeClassWithoutSwift做简化理解如下:

  • cls->data()中读取类的信息,如果是 future class ,对rwro赋值;如果是正常的类,则开辟rw空间,对ro进行赋值,并拷贝到rw中。

  • 递归实现类的父类以及元类,随后更新该类的父类和元类以备重新映射。确保继承链以及isa链的完整性。

  • ro中赋值一些标志到rw中。

  • 如果父类存在,则将这个类链接到它的父类的子类列表,否则作为一个新的根类。

  • 执行methodizeClass

1.1.2.1 methodizeClass

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

    // 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.
    method_list_t *list = ro->baseMethods();
    if (list) {
        prepareMethodLists(cls, &list, 1, YES, isBundleClass(cls));
        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);
    }

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

methodizeClass方法中,主要做了这些事情:

  • ① install 类的方法列表(method_list_t)
  • ② install 类的属性列表(property_list_t)
  • ③ install 类的协议列表(protocol_list_t)
  • ④ Attach categories.
  • install 类的方法列表(method_list_t)
method_list_t *list = ro->baseMethods();
    if (list) {
        prepareMethodLists(cls, &list, 1, YES, isBundleClass(cls));
        if (rwe) rwe->methods.attachLists(&list, 1);
    }

ro中读取baseMethods(),如果列表有值,通过prepareMethodLists对方法列表进行预处理

static void 
prepareMethodLists(Class cls, method_list_t **addedLists, int addedCount,
                   bool baseMethods, bool methodsFromBundle)
{
    runtimeLock.assertLocked();
    if (addedCount == 0) return;
    if (baseMethods) {
        ASSERT(cls->hasCustomAWZ() && cls->hasCustomRR() && cls->hasCustomCore());
    }
    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*/);
        }
    }
    if (cls->isInitialized()) {
        objc::AWZScanner::scanAddedMethodLists(cls, addedLists, addedCount);
        objc::RRScanner::scanAddedMethodLists(cls, addedLists, addedCount);
        objc::CoreScanner::scanAddedMethodLists(cls, addedLists, addedCount);
    }
}

对于某些类的方法会存在 RR/AWZ/Core 这样的特殊情况,但是我们不需要做任何处理,因为默认的 RR/AWZ/Core 是不会在setInitialized() 方法执行之前设置的。

之后通过fixupMethodList对方法列表进行排序,这也是我们在进行方法查找流程中对方法列表进行二分查找的前提:保证方法列表中的方法是有序的。

static void 
fixupMethodList(method_list_t *mlist, bool bundleCopy, bool sort)
{
    runtimeLock.assertLocked();
    ASSERT(!mlist->isFixedUp());
    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) {
        method_t::SortBySELAddress sorter;
        std::stable_sort(mlist->begin(), mlist->end(), sorter);
    }
    
    // Mark method list as uniqued and sorted
    mlist->setFixedUp();
}

fixup 做了两件事:UniqueSort.

Unique: 保证方法的唯一性;
Sort: 对方法列表进行排序:排序的过程是根据方法的name进行排序;

struct SortBySELAddress :
      public std::binary_function<const method_t&,
                                   const method_t&, bool>
   {
       bool operator() (const method_t& lhs,
                        const method_t& rhs)
       { return lhs.name < rhs.name; }
   };

最后标记方法列表为已修复状态,后续不需再进行fixup了。

prepareMethodLists的工作完成后,判断rwe是否存在,如果存在,则rwe->methods.attachLists。而此时rwe是不存在的,这里补充说明一下:

Tips:

iOS 类的结构分析 这篇文章中,我们已知:类在编译期时,类的一些数据信息保存在 ro 中,包含了类的名称,方法,协议,实例变量等 编译期确定 的信息。当类被Runtime加载之后,runtime会为它分配额外的用于 读取/写入rw

ro 是只读的,存放的是 编译期间就确定 的字段信息;而 rw 是在 runtime 时才创建的,它会先将 ro 的内容拷贝一份,再将类的分类、属性、方法、协议等信息添加进去。

而对于存在动态更改行为的类,会将这部分动态的内容提取到 rwe 中。

到目前为止,类的加载过程,对于 ro, rw 均已赋值完毕,而 rwe 的赋值是要在类存在动态更改行为时触发的。此时rwe还不存在,所以并不需要rwe->methods.attachLists。那么rwe是何时被赋值的呢?

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 的创建是通过extAllocIfNeeded。搜索extAllocIfNeeded可得到下面的结果。

上午11.51.25.png

分别对应了:attachCategoriesdemangledNameclass_setVersionaddMethodaddMethodsclass_addProtocol_class_addPropertyobjc_duplicateClass 。无一不是动态修改的行为。

如果rwe已创建,且方法列表有值,attachLists(&list, 1)

  • install 类的属性列表(property_list_t)
property_list_t *proplist = ro->baseProperties;
    if (rwe && proplist) {
        rwe->properties.attachLists(&proplist, 1);
    }

如果rwe已创建,且属性列表有值,attachLists(&proplist, 1)

  • install 类的协议列表(protocol_list_t)
protocol_list_t *protolist = ro->baseProtocols;
    if (rwe && protolist) {
        rwe->protocols.attachLists(&protolist, 1);
    }

如果rwe已创建,且协议列表有值,attachLists(&protolist, 1)

可见只要满足条件,都会执行attachLists函数

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

        if (hasArray()) {
            // many lists -> many lists
            uint32_t oldCount = array()->count;
            uint32_t newCount = oldCount + addedCount;
            setArray((array_t *)realloc(array(), array_t::byteSize(newCount)));
            array()->count = newCount;
            memmove(array()->lists + addedCount, array()->lists, 
                    oldCount * sizeof(array()->lists[0]));
            memcpy(array()->lists, addedLists, 
                   addedCount * sizeof(array()->lists[0]));
        }
        else if (!list  &&  addedCount == 1) {
            // 0 lists -> 1 list
            list = addedLists[0];
        } 
        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;
            memcpy(array()->lists, addedLists, 
                   addedCount * sizeof(array()->lists[0]));
        }
    }

attachLists函数区分了三种情况,我们按照执行流程分析如下:

  • 【第一种】0 lists --> 1 list
    从无到有的过程,lists中还未有任何内容,此时直接将 new list 内容赋值过来即可。
  • 【第二种】1 list -> many lists
    此时 lists 中已有【第一种】 中赋值的数组。要添加的大小与原大小相加计算出所需容量大小,根据容量开辟 array()空间并赋值:将旧的内容(oldList)放在数组末尾,新的内容(addedLists)从lists的首地址开始,大小为addedCount 个空间,拷贝到lists中,这样,旧数据放最后,新数据整体放在前。
  • 【第三种】many lists -> many lists
    在 【第二种】 中,array() 已赋值,所以当有新的数组 attach 时,重新开辟array()空间,大小依然是旧数组容量与新数据容量之和。将旧数据移动到最后,新数据整体拷贝到旧数据的前面。
  • Attach categories
// Attach categories.
    if (previously) {
        if (isMeta) {
            objc::unattachedCategories.attachToClass(cls, previously,
                                                     ATTACH_METACLASS);
        } else {
            objc::unattachedCategories.attachToClass(cls, previously,
                                                     ATTACH_CLASS_AND_METACLASS);
        }
    }
    objc::unattachedCategories.attachToClass(cls, cls,
                                             isMeta ? ATTACH_METACLASS : ATTACH_CLASS);

Attach categories 是添加分类的过程,此部分内容放在 iOS 分类详解 中探讨。

至此为止,类的加载过程(除分类以外)大致完成,通过上面这些步骤,类的结构已大致完备。
通过 readClass 将类的地址与名字进行关联并且载入内存;
通过 realizeClassWithoutSwift 对类的 rorw 完成赋值;
通过 methodizeClass 对类的方法、属性、协议进行完备,并且完成分类的加载。
当存在动态修改等行为时,也会将类的动态修改部分存入rwe

这些都是在 map_images 中完成的。下面我们接着研究load_images

2. load_images

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

void
load_images(const char *path __unused, const struct mach_header *mh)
{
    if (!didInitialAttachCategories && didCallDyldNotifyRegister) {
        didInitialAttachCategories = true;
        loadAllCategories();
    }
    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);
    }

    // Call +load methods (without runtimeLock - re-entrant)
    call_load_methods();
}

首先,如果 libobjc.A.dylib 动态库的镜像文件映射完毕且首次执行load_images的时候,会调用loadAllCategories() 函数,完成所有分类的加载。(分类相关的内容本章暂不探讨,放在 iOS分类详解 里统一探讨);

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

2.1 prepare_load_methods

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

prepare_load_methods 做了两件事:

    1. 从当前动态库的【MachO】header中读取__objc_nlclslist,执行 schedule_class_load;
    1. 从当前动态库的【MachO】header中读取__objc_nlcatlist,随后迫使主类实现,继而执行add_category_to_loadable_list,将待执行+load的分类添加到loadable_categories表中。

2.1.1 schedule_class_load

static void schedule_class_load(Class cls)
{
    if (!cls) return;
    ASSERT(cls->isRealized());  // _read_images should realize
    // 如已执行add_class_to_loadable_list 则不需继续添加
    if (cls->data()->flags & RW_LOADED) return; 

    // 确保父类先执行
    schedule_class_load(cls->superclass);  
    // 插表
    add_class_to_loadable_list(cls);
    // 设置标记:cls已执行add_class_to_loadable_list
    cls->setInfo(RW_LOADED);    
}

对于不存在的类或者未实现的类进行拦截,同时对于已执行过add_class_to_loadable_list 函数的类也进行过滤。当满足条件向下执行的时候,要先确保该类的父类先于该类执行+load(如果父类实现了+load),随后执行add_class_to_loadable_list 插表操作,并且标记cls为已完成插表操作。

2.1.1.1 add_class_to_loadable_list

void add_class_to_loadable_list(Class cls)
{
    IMP method;
    loadMethodLock.assertLocked();
    // 取到要存入loadable_classes表的类 +load方法的 IMP
    method = cls->getLoadMethod();  
    if (!method) return;  // Don't bother if cls has no +load method
    
    if (PrintLoading) {
        _objc_inform("LOAD: class '%s' scheduled for +load", 
                     cls->nameForLogging());
    }

    // 空表或者达最大容量,则开辟或扩容 ,每个动态库中的所有+load方法会存储在同一个表中
    if (loadable_classes_used == loadable_classes_allocated) {
        loadable_classes_allocated = loadable_classes_allocated*2 + 16;
        loadable_classes = (struct loadable_class *)
            realloc(loadable_classes,
                              loadable_classes_allocated *
                              sizeof(struct loadable_class));
    }
    
    loadable_classes[loadable_classes_used].cls = cls;
    loadable_classes[loadable_classes_used].method = method;
    loadable_classes_used++;
}

通过getLoadMethod()cls中的ro()->baseMethods(),遍历查询load方法,找到则返回方法的 IMP,执行插表操作,未找到直接return.

每个动态库中需要执行+load的类会存入到同一张表loadable_classes,根据已占用空间和开辟空间进行对比,以判断是够需要扩容重新开辟空间。

2.1.2 add_category_to_loadable_list

void add_category_to_loadable_list(Category cat)
{
    IMP method;
    loadMethodLock.assertLocked();

    method = _category_getLoadMethod(cat);
    // Don't bother if cat has no +load method
    if (!method) return;

    if (PrintLoading) {
        _objc_inform("LOAD: category '%s(%s)' scheduled for +load", 
                     _category_getClassName(cat), _category_getName(cat));
    }
    
    if (loadable_categories_used == loadable_categories_allocated) {
        loadable_categories_allocated = loadable_categories_allocated*2 + 16;
        loadable_categories = (struct loadable_category *)
            realloc(loadable_categories,
                              loadable_categories_allocated *
                              sizeof(struct loadable_category));
    }

    loadable_categories[loadable_categories_used].cat = cat;
    loadable_categories[loadable_categories_used].method = method;
    loadable_categories_used++;
}

分类和主类的插表操作大致相同的,通过_category_getLoadMethod 从分类的cat->classMethods中查找+load方法,找到则插入loadable_categories表。

这里需要注意的是在执行add_category_to_loadable_list 操作之前,先要完成主类的实现realizeClassWithoutSwift

2.2 call_load_methods()

prepare_load_methods 函数,将类的+load,父类的+load,分类的+load,都以准备完毕,接下来就要执行call_load_methods()

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 {
        // 1. Repeatedly call class +loads until there aren't any more
        while (loadable_classes_used > 0) {
            call_class_loads();
        }

        // 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方法。

这样,+load 方法得以被调用。

3. 总结

本篇文章主要是从_objc_init开始,结合map_imagesload_images 来探究类的加载过程。 如果你对 dyld 还不是很熟悉,建议先看一看我的另一篇文章 iOS 应用程序加载 ,再来看这篇文章的时候会更清晰些。

尽管类的加载细节是相同的,但加载的【时机】却不尽相同,为此有必要在这里也补充一下 懒加载类 与 非懒加载类。

补充:懒加载类与非懒加载类

非懒加载类:随着程序启动,在map_iamges阶段既完成加载的类.
懒加载类:当第一次使用类时才加载的类(通常为第一次发送消息时)。

我们可以通过实现+load方法来迫使类提前加载,即非懒加载类。

对于几种实现+load方法的情况的堆栈情况如下图:

  • 主类实现+load

当主类实现+load 的时候,在_read_images的第⑨步中就会实现类。

  • 子类实现+load

当子类实现+load时,上图堆栈中会执行两次realizeClassWithoutSwift,如果前面探讨的内容你有仔细看的话,应该记得,在realizeClassWithoutSwift方法内,为了保证继承链的完整性,会递归调用realizeClassWithoutSwift,去实现父类。

  • 分类实现+load

分类实现+load 时,在prepare_load_methods时,会先调用realizeClassWithoutSwift,确保本类先实现。所以堆栈信息是这样的。

  • 均未实现+load

当上述+load均未实现的时候,类的实现将直到第一次使用它时,这里的情况一般就是发送消息的时候了。在消息的慢速查找流程中,如果类还没有实现,为了保证程序的正常运行,会在这里实现类,这也是符合正常逻辑的。

才疏学浅,文章如有误导之处,还请指正,若觉得对你有所帮助,也可以给我一个赞😁 感谢!

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