类的加载

562 阅读11分钟

1、什么是类?

类在面向对象编程中是一种面向对象计算机编程语言的构造,是创建对象的蓝图,描述了所创建的对象共同的属性和方法。对于 Object-C 来说,类中除了属性和方法还放了协议等。

2、什么是类的加载

在看接下来的内容时,如果你对 App 的启动流程不理解的话,建议看看 App启动流程之 dyld 探析

打开 mach-o 文件后发现,类是在编译时就确定地址的,只不过是一个相对地址,需要在运行时通过 ASLR 的修正才能拿到正确的内存地址。

类的加载是把修正后的类地址加入到内存中,并且将类的地址和类的名称一一对应,将类中的属性、协议、方法等进行读取和保存到哈希表中,方便下次调用。

App启动流程之 dyld 探析 知道,类是在 App 启动的时候回调 _dyld_objc_notify_register(&map_images, load_images, unmap_image); 方法进行加载的,本文研究对象就是这些如何被苹果落实的。

  • map_images :将类的地址、类和分类中的属性、协议等映射到哈希表中;
  • load_images:主要调用类和分类的 load 方法。

3、map_images

经过 map_images -> map_images_nolock -> _read_images 的源码查看,核心代码主要在 _read_images 中。

map_images_nolock 源码计算了 mach_header 中的 count,传给了 _read_images 函数。

主要源码如下:

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

   
    static bool doneOnce;

#define EACH_HEADER \
    hIndex = 0;         \
    hIndex < hCount && (hi = hList[hIndex]); \
    hIndex++

    if (!doneOnce) {
        doneOnce = YES;
       
        //判断是否禁用 TaggedPointers
        if (DisableTaggedPointers) {
            disableTaggedPointers();
        }
        
        initializeTaggedPointerObfuscator();

        // 根据资源总数确定开辟空间数
        int namedClassesSize = 
            (isPreoptimized() ? unoptimizedTotalClasses : totalClasses) * 4 / 3;
        
        /// 创建保存类的哈希表
        gdb_objc_realized_classes =
            NXCreateMapTable(NXStrValueMapPrototype, namedClassesSize); 
    }

    // 方法的加载的修复
    static size_t UnfixedSelectors;
    {
      
        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;
             }
         }
    }

    ts.log("IMAGE TIMES: fix up selector references");
    
    
    // 将类的列表从 mach-o 中读取出来
    for (EACH_HEADER) {
       
        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];
            
            // 将类插入到 gdb_objc_realized_classes 表中,将名称对应
            Class newCls = readClass(cls, headerIsBundle, headerIsPreoptimized);

            if (newCls != cls  &&  newCls) {
                // ...
                
                // 如果当前处理后的类和原始类不一样就需要重新初始化一下
            }
        }
    }

    ts.log("IMAGE TIMES: discover classes");

    // Fix up remapped classes
    // 修复重映射的类
    
    // ...

    ts.log("IMAGE TIMES: remap classes");

    //  加载协议
    for (EACH_HEADER) {
       // ...
       
        protocol_t * const *protolist = _getObjc2ProtocolList(hi, &count);
        for (i = 0; i < count; i++) {
            readProtocol(protolist[i], cls, protocol_map, 
                         isPreoptimized, isBundle);
        }
    }

    ts.log("IMAGE TIMES: discover protocols");

    // Fix up @protocol references
    // 修复@protocol引用
    
    // ...

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

  
    // 发现类别。只有在完成了初始的类别附件后才能这样做。
    // 对于在启动时出现的类别,发现延迟到对_dyld_objc_notify_register的调用完成后的第一次load_images调用。
    // rdar: / /问题/ 53119145
    if (didInitialAttachCategories) {
        for (EACH_HEADER) {
            load_categories_nolock(hi);
        }
    }

    ts.log("IMAGE TIMES: discover categories");


    // 加载非懒加载类,用于实现了 +load 方法 和 静态实例
    for (EACH_HEADER) {
        classref_t const *classlist = hi->nlclslist(&count);
        for (i = 0; i < count; i++) {
            Class cls = remapClass(classlist[i]);
            if (!cls) continue;

            addClassTableEntry(cls);

            // 实现类
            realizeClassWithoutSwift(cls, nil);
        }
    }

    ts.log("IMAGE TIMES: realize non-lazy classes");

    // Realize newly-resolved future classes, in case CF manipulates them
    // 未来类的加载
    
    // ...

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

    if (DebugNonFragileIvars) {
        realizeAllClasses();
    }

    // ...

#undef EACH_HEADER
}

通过上方代码的分析,发现 map_images 的主流程主要干了以下事情:

  • 加载所有类到 gdb_objc_realized_classes 表中;
  • 对所有类做重映射;
  • 将所有 SEL 都注册到 namedSelectors 表中;
  • 修复函数指针遗留;
  • Protocol 都添加到 Protocol_map 表中;
  • 对所有 Protocol 做重映射;
  • 对已经初始化的类加载分类,未初始化的类延迟到 load_images
  • 初始化所有非懒加载类,对 ro 进行排序,包括当前非懒加载类的所有父类和元类;
  • 处理所有的非懒加载类 Category,包括 ClassMeta Class

4、readClass

如果对于当前从 mach-o 中读取的类,readClass 仅仅是把类的名称读取出来,把类的地址和名称插入 gdb_objc_realized_classes 表中,但是对于 popFutureNamedClass ,这里是会读 rorw 的。

源码如下:

Class readClass(Class cls, bool headerIsBundle, bool headerIsPreoptimized)
{
    const char *mangledName = cls->nonlazyMangledName();
    
    // ...
    
    Class replacing = nil;
    if (mangledName != nullptr) {
        if (Class newCls = popFutureNamedClass(mangledName)) {
            // 对于 popFutureNamedClass 会进行 读ro 写 rw ,但是普通从 mach-o 读取的不会
           
            class_rw_t *rw = newCls->data();
            const class_ro_t *old_ro = rw->ro();
            memcpy(newCls, cls, sizeof(objc_class));

            // Manually set address-discriminated ptrauthed fields
            // so that newCls gets the correct signatures.
            newCls->setSuperclass(cls->getSuperclass());
            newCls->initIsa(cls->getIsa());
 
            rw->set_ro((class_ro_t *)newCls->data());
            newCls->setData(rw);
            freeIfMutable((char *)old_ro->getName());
            free((void *)old_ro);

            addRemappedClass(cls, newCls);

            replacing = cls;
            cls = newCls;
        }
    }
    
    if (headerIsPreoptimized  &&  !replacing) {
        // ...
    } else {
        if (mangledName) { 
            // some Swift generic classes can lazily generate their names 
            // 一些快速的泛型类可以惰性地生成它们的名称
            
            //将类和类名称关联
            addNamedClass(cls, mangledName, replacing);
        } else {
            Class meta = cls->ISA();
            const class_ro_t *metaRO = meta->bits.safe_ro();
        }
        
        // 在插入表中一次,防止漏插,插入的就不会重复插入
        addClassTableEntry(cls);
    }
    return cls;
}

5、realizeClassWithoutSwift

实现非懒加载的类,不包含 Swift ,为什么说只是实现非懒加载类呢?

开发中,将一些可能初始化用不到的属性或者对象懒加载以节约内存,当第一次使用属性或者对象的时候再初始化,对于类也是同样道理。

懒加载类和非懒加载类的区别方式:是否实现 +(void)load {} 方法。

源码如下(添加了注释):

static Class realizeClassWithoutSwift(Class cls, Class previously)
{
    runtimeLock.assertLocked();

    class_rw_t *rw;
    Class supercls;
    Class metacls;

    if (!cls) return nil;
    if (cls->isRealized()) {
        validateAlreadyRealizedClass(cls);
        return cls;
    }
    ASSERT(cls == remapClass(cls));

    // fixme verify class is not in an un-dlopened part of the shared cache?

    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.
        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>();
        
        // 把 ro 给 rw
        rw->set_ro(ro);
        
        // 类已经开始实现但还没有完成它
        rw->flags = RW_REALIZED|RW_REALIZING|isMeta;
        
        // 给类的data.bits 赋值
        cls->setData(rw);
    }

    //初始化为空或在伪装中预优化
    cls->cache.initializeToEmptyOrPreoptimizedInDisguise();

#if FAST_CACHE_META
    // 非正规化的RO_META,以避免间接
    if (isMeta) cls->cache.setBit(FAST_CACHE_META);
#endif

    // Choose an index for this class.
    // Sets cls->instancesRequireRawIsa if indexes no more indexes are available
    
    // 为类选择索引。
    cls->chooseClassArrayIndex();

    // 初始化父类
    supercls = realizeClassWithoutSwift(remapClass(cls->getSuperclass()), 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.
        
        // 元类不需要任何来自非指针ISA的特性
        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->getName(), "OS_object"))
        {
            // hack for libdispatch et al - isa also acts as vtable pointer
            hackedDispatch = true;
            instancesRequireRawIsa = true;
        }
        else if (supercls  &&  supercls->getSuperclass()  &&
                 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) {
            // 将这个类及其所有子类标记为需要原始isa指针
            cls->setInstancesRequireRawIsaRecursively(rawIsaIsInherited);
        }
    }
// SUPPORT_NONPOINTER_ISA
#endif

    // Update superclass and metaclass in case of remapping
    
    // 设置父类
    cls->setSuperclass(supercls);
    
    // 初始化isa
    cls->initClassIsa(metacls);

    // Reconcile instance variable offsets / layout.
    // 调和实例变量的偏移量。
    
    // This may reallocate class_ro_t, updating our ro variable.
    // 可以重新分配class_ro_t,更新ro变量。
    
    if (supercls  &&  !isMeta) reconcileInstanceVariables(cls, supercls, ro);

    // Set fastInstanceSize if it wasn't set already.
    // 如果还没有设置fastInstanceSize,则设置它。
    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.
    
    //从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(cls, previously);

    return cls;
}

通过上方代码的分析,发现 realizeClassWithoutSwift 的主流程主要干了以下事情:

  • class_rw_t *rw 开辟空间;
  • class_ro_t *ro 赋值给 class_rw_t *rw 中的 ro
  • 标记当前类的状态,类已经开始实现但还没有完成它;
  • rwclsdata.bits
  • 如果子类是非懒加载的,那么其元类、父类、父元类都需要递归初始化;
  • 将子类和父类进行双向链接;
  • 元类初始化 isa 禁用 nonpointerIsa
  • rosuperclass 集成相关的对象禁用标志;
  • 条理化类、加载分类。

6、methodizeClass

1、class_rw_ext_t *rwe

WWDC20 之前苹果为了方便开发者使用runtimeAPI 对类的 rw 进行修改,在每个类进行初始化的时候都会将 class_ro_t *ro方法列表协议属性 等复制一份到 rw 中去。如下图(来自这篇博客):

image.png

但是苹果经过数据调研发现,大多 App 中 90% 的类都没有对 class_rw_t *rw 进行操作过。

所以从 WWDC20 后,苹果将 class_rw_t 进行了优化和拆分,把方法列表协议属性 等单独申请了一个 class_rw_ext_t *rwe ,只有在使用的时候才会初始化它,减少占用内存,优化内存。优化后如下图:

image.png

也就是说,从 WWDC20 如果没有对类进行动态添加,取值的时候就直接从 class_ro_t *ro 进行获取了,有了上面这个解释,下面的有些地方就容易理解了。

之前看过 objc-752 记得会把 ro 的东西复制到 rw ,看了半天 rwe 怎么都是空的,就这里坑了好久,以为自己记错了,Google 后发现这里2020年修改了,整个人

2、methodizeClass

这里在研究自己写的类的时候,没有给类动态添加方法,这里的 class_rw_ext_t *rwe 都为 NULL。只有 rwe 有值的时候才会从 ro方法列表协议属性 等复制到 rwe

源码如下:

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
  
    // Install methods and properties that the class implements itself.
    method_list_t *list = ro->baseMethods();
    if (list) {
        prepareMethodLists(cls, &list, 1, YES, isBundleClass(cls), nullptr);
        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);
}

3、prepareMethodLists

判断类中的方法是否需要排序,排序是通过 fixupMethodList 排序的。

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

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

   // ...
}

4、fixupMethodList

selector 的地址进行排序。

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.setName(sel_registerNameNoLock(name, bundleCopy));
        }
    }

    // Sort by selector address.
    // 将 selector 的地址进行排序。
    if (sort && !mlist->isSmallList() && mlist->entsize() == method_t::bigSize) {
        method_t::SortBySELAddress sorter;
        std::stable_sort(&mlist->begin()->big(), &mlist->end()->big(), sorter);
    }
    
    // Mark method list as uniqued and sorted.
    // Can't mark small lists, since they're immutable.
    if (!mlist->isSmallList()) {
        mlist->setFixedUp();
    }
}

7、分类加载

1、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);

    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(cls, list.array(), list.count(), flags);
        }
        map.erase(it);
    }
}

2、attachCategories

static void
attachCategories(Class cls, const locstamped_category_t *cats_list, uint32_t cats_count,
                 int flags)
{
    // ...
    
    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, __func__);
                
                // rwe 调用 attachLists 添加方法
                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, __func__);
        // 进行内存平移操作
        rwe->methods.attachLists(mlists + ATTACH_BUFSIZ - mcount, mcount);
        if (flags & ATTACH_EXISTING) {
            flushCaches(cls, __func__, [](Class c){
                return !c->cache.isConstantOptimizedCache();
            });
        }
    }

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

    rwe->protocols.attachLists(protolists + ATTACH_BUFSIZ - protocount, protocount);
}

3、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;
        
        //开辟新的 newCount 的空间
        array_t *newArray = (array_t *)malloc(array_t::byteSize(newCount));
        
        // 重新赋值 count
        newArray->count = newCount;
        array()->count = newCount;
        
        for (int i = oldCount - 1; i >= 0; i--)
            // 把旧数组中的方法按照顺序,放到新数组后方
            newArray->lists[i + addedCount] = array()->lists[i];
            
        for (unsigned i = 0; i < addedCount; i++)
            //把新数组中的方法放到前方
            newArray->lists[i] = addedLists[i];
        
        // 释放旧数组
        free(array());
        
        // 设置新数组
        setArray(newArray);
        
        validate();
    }
    else if (!list  &&  addedCount == 1) {
        // 0 lists -> 1 list
        list = addedLists[0];
        validate();
    }
    else {
        // 1 list -> many lists
        
        // 如果没有原来的数组,那么这个数组就是分类数组,直接开辟空间并存储
        
        Ptr<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;
        
        for (unsigned i = 0; i < addedCount; i++)
            array()->lists[i] = addedLists[i];
            
        validate();
    }
}

attachLists 方法中,看到一个重要信息,在给主类添加分类方法的时候是将旧数组中的方法列表后移,而新的方法列表在前。

根据编译流程,1 个主类有 10 个分类的话,那么第 10 个分类的方法同名方法先执行,后续的就不执行了。是因为在方法查找的时候,一旦查找到了,就直接返回了,而不是最后的分类方法覆盖前面的所有同名方法,只是不找了而已。

8、类和分类的搭配使用

类型分类 实现了 load分类 未实现 load
主类 实现了 loadload_images加载分类编译时将分类编译到主类中
主类 未实现 load迫使主类先初始化,load_images加载分类编译时将分类编译到主类中

1、非懒加载类+非懒加载分类

这个是最正常的流程,但是是开发中基本不会出现的流程。

源码调用流程如下:

map_images -> map_images_nolock -> _read_images -> readClass -> _getObjc2NonlazyClassList -> realizeClassWithoutSwift -> methodizeClass -> load_images -> loadAllCategories -> load_categories_nolock -> load_categories_nolock -> attachCategories -> attachLists

2、懒加载类+非懒加载分类

开发中处理 method_swizzing 经常使用这个流程,因为分类非懒加载,所以迫使主类提前加载,走的上一个流程。

3、非懒加载类+懒加载分类

主类在 read_images 中 通过 realizeClassWithoutSwift 实现,分类的方法在编译时直接编译到主类中。

4、懒加载类+懒加载分类

主类第一次调用的时候在 lookUpImpOrForward 时实现,分类的方法在编译时直接编译到主类中。

9、load_images

如果分类实现了 load 方法,loadAllCategories 就会被调用,将分类中的方法加载到主类中去。

然后调用主类 load 在调用分类 load

void
load_images(const char *path __unused, const struct mach_header *mh)
{
    if (!didInitialAttachCategories && didCallDyldNotifyRegister) {
        didInitialAttachCategories = true;
        
        // 如果分类 实现了 load 就会调用,将分类中的方法加载到主类中去
        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);
    }

    // Call +load methods (without runtimeLock - re-entrant)
    
    // 调用所有实现了 load 方法的类
    call_load_methods();
}

这里有一个问题:如果有人问主类和分类实现了同名方法,执行谁?

答:如果同名方法为 load 方法,先执行主类,后执行分类。如果其他方法,按照编译顺序,实现最后一个分类的同名方法,因为新添加的分类方法在当前类的方法的最前方,方法查找时,找到第一个就不再往后查找了。

load 方法,先执行主类,后执行分类的原因如下:

上方看到了 load_images 的实现的方法,只有 didCallDyldNotifyRegistertrue 的时候才能执行 loadAllCategories() 方法,但是 didCallDyldNotifyRegister 设置为 true 是在 objc_init _dyld_objc_notify_register(&map_images, load_images, unmap_image); 方法之后, 所以先执行了主类方法。

void _objc_init(void)
{
   // ...

    _dyld_objc_notify_register(&map_images, load_images, unmap_image);

#if __OBJC2__
    didCallDyldNotifyRegister = true;
#endif
}

10、总结

到这里看类的加载就结束了,研究完类的加载这里,有一些小启发:

  • 主类和分类能不实现 load 方法就不实现 load 方法,影响启动时间;

  • 分类能不实现 load 方法就不实现 load 方法,影响启动时间;

  • 如果继承链比较长,子类最好不要实现 load 方法,因为子类实现了 load 方法,那么当前继承链的类都要被实现。

感谢阅读,如果对你有帮助,希望给个赞,如果有错误的地方,希望大佬指出,共同学习,一起进步,谢谢。