美文网首页
15.iOS底层学习之类的加载分析

15.iOS底层学习之类的加载分析

作者: 牛牛大王奥利给 | 来源:发表于2021-10-15 17:49 被阅读0次

    上一篇文章学习了read_images相关的流程,发现类的初始和方法realizeClassWithoutSwift有关系。这篇文章讲重点分析realizeClassWithoutSwift里面的具体操作,主要去学习以下几个我自己比较关心的问题:
    1、realizeClassWithoutSwift的主要功能
    2、rw是什么时候被赋值的?
    3、懒加载类和非懒加载类的区别
    4、分类的本质是?结构?
    5、分类中的方法是如何被加载的?

    realizeClassWithoutSwift

    /***********************************************************************
    * realizeClassWithoutSwift
    * Performs first-time initialization on class cls,  为了类的首次执行进行初始化
    * including allocating its read-write data. 包括分配读写数据
    * Does not perform any Swift-side initialization.不执行任何Swift端初始化
    * Returns the real class structure for the class. 返回类的实际类结构
    * Locking: runtimeLock must be write-locked by the caller.锁:runtimeLock必须由调用方进行写锁定
    **********************************************************************/
    static Class realizeClassWithoutSwift(Class cls, Class previously)
    {
        runtimeLock.assertLocked();
        class_rw_t *rw;
        Class supercls;
        Class metacls;
    //cls不存在不往下执行了,返回nil
        if (!cls) return nil;
    //判断cls是不是被实现过了(可能有别的线程调用过实现这个类),为了防止并发实现过了这个类,要加锁
    // Locking: To prevent concurrent realization, hold runtimeLock.
        if (cls->isRealized()) {
            //去验证下被实现过的类,内部进行了是不是有脏数据添加进去的判断。
            validateAlreadyRealizedClass(cls);
            return cls;
        }
    
        // fixme verify class is not in an un-dlopened part of the shared cache?fixme verify类不在共享缓存的未打开部分中
    
        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数据已经被分配
            rw = cls->data(); //直接读取rw数据
            ro = cls->data()->ro();//直接读取ro数据
            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中的ro
            rw->flags = RW_REALIZED|RW_REALIZING|isMeta;//赋值标志位
            cls->setData(rw);//rw赋值
        }
    
    //初始化或者清空cache
        cls->cache.initializeToEmptyOrPreoptimizedInDisguise();
    
        // Choose an index for this class.
        // Sets cls->instancesRequireRawIsa if indexes no more indexes are available
      //为此类选择一个索引。
      //如果索引不再可用,则设置cls->instancesRequireRawIsa
        cls->chooseClassArrayIndex();
    .....
        // Realize superclass and metaclass, if they aren't already.实现超类和元类,如果它们还没有实现的话。
        // This needs to be done after RW_REALIZED is set above, for root classes.这需要在上面为根类设置RW_之后完成。
        // This needs to be done after class index is chosen, for root metaclasses.对于根元类,这需要在选择类索引之后完成。
        supercls = realizeClassWithoutSwift(remapClass(cls->getSuperclass()), nil);
        metacls = realizeClassWithoutSwift(remapClass(cls->ISA()), nil);
    
    ......
    //省略这个部分是一些关于SUPPORT_NONPOINTER_ISA的一些环境设置操作和赋值
    ......
    
        // Update superclass and metaclass in case of remapping 在重新映射的情况下更新超类和元类
        cls->setSuperclass(supercls);
        cls->initClassIsa(metacls);
    
        // Reconcile instance variable offsets / layout. 协调实例变量偏移/布局。
        // This may reallocate class_ro_t, updating our ro variable.这可能会重新分配类,更新我们的ro变量
        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  将一些标志从ro复制到rw
        if (ro->flags & RO_HAS_CXX_STRUCTORS) {
            cls->setHasCxxDtor();
            if (! (ro->flags & RO_HAS_CXX_DTOR_ONLY)) {
                cls->setHasCxxCtor();
            }
        }
       
    
        // 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方法的阅读,大致了解了它的主要功能:
    ·完成了rw的赋值,cls->setData(rw);
    ·初始化或者清空了cache,cls->cache.initializeToEmptyOrPreoptimizedInDisguise();
    ·实现了父类和元类, cls->setSuperclass(supercls); cls->initClassIsa(metacls);
    ·将该类连接到其父类的子类列表 ,addSubclass(supercls, cls);
    ·关联分类,methodizeClass;

    懒加载类和非懒加载类

    在学习read_images的时候里面有关于非懒加载类的注释说明,我们来详细看看懒加载类和非懒加载类有什么差别。

    non-lazy classes

    这段注释下面的代码如下(这是截取方法read_images的部分代码):

     // Realize non-lazy classes (for +load methods and static instances)
        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);
    
                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
                }
                realizeClassWithoutSwift(cls, nil);
            }
        }
    

    根据上面代码的注释,实现非懒加载类(实现了+load方法和静态实例的)。也就是说一个类实现了+load方法会走这个非懒加载的类的的实现。我们通过断点结合+load的方法实现与否可以验证这段代码的执行和+load确实有关系。

    了解了非懒加载类,那么接下来研究懒加载类是个什么东西和加载流程。根据+load的实现与否来看,实现了是非懒加载,那么没实现的应该就是懒加载类。我们去掉+load来进行调试研究下懒加载类的调用过程。

    image.png
    因为是不是懒加载的类都会走方法realizeClassWithoutSwift,所以我们通过调试方法realizeClassWithoutSwift,发现realizeClassWithoutSwift的调用是由lookUpImpOrForward发起调用的,通过realizeAndInitializeIfNeeded_locked->realizeClassMaybeSwiftAndLeaveLocked->realizeClassMaybeSwiftMaybeRelock->realizeClassWithoutSwift这个过程来调用。
    所以我们可以了解到,懒加载的类是在第一次被使用到的时候被加载到映射表里。
    ·非懒加载类调用过程
    map_images
    map_images_nolock
    _read_images
    realizeClassWithoutSwift
    而在_dyld_objc_notify_register调用的时候还有一个load_images,会去调用load方法,流程是:
    load_images
    hasLoadMethods
    _getObjc2NonlazyClassList
    prepare_load_methods
    call_load_methods
    ·懒加载类调用过程,是在类第一次被使用的时候调用:
    lookUpImpOrForward
    realizeAndInitializeIfNeeded_locked
    realizeClassMaybeSwiftAndLeaveLocked
    realizeClassMaybeSwiftMaybeRelock
    realizeClassWithoutSwift

    分类

    在上面的方法realizeClassWithoutSwift关于分类的部分是最后的分类关联,方法methodizeClass

    methodizeClass
    /***********************************************************************
    * 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();
    
        // Install methods and properties that the class implements itself.
    //🍎方法列表的获取,然后⚠️attachLists
        method_list_t *list = ro->baseMethods();
        if (list) {
            prepareMethodLists(cls, &list, 1, YES, isBundleClass(cls), nullptr);
            if (rwe) rwe->methods.attachLists(&list, 1);
        }
    
    //🍎属性列表的获取,然后⚠️attachLists
        property_list_t *proplist = ro->baseProperties;
        if (rwe && proplist) {
            rwe->properties.attachLists(&proplist, 1);
        }
    
    //🍎协议列表的获取,然后⚠️attachLists
        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. 
     //🍎关联分类,attachToClass
        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);
    
    }
    

    通过方法注释了解到Fixes up cls's method list, protocol list, and property list.:会修复类的方法列表协议列表还有属性列表,会关联上分类。
    上面源代码中有几个关键的方法来简单的介绍下功能:
    prepareMethodLists:该方法会调用fixupMethodList->stable_sort,而stable_sort会根据传进来的第三个参数进行排序。

    method_t::SortBySELAddress sorter;
    std::stable_sort(&mlist->begin()->big(), &mlist->end()->big(), sorter);
    

    SortBySELAddress是一个结构体,里面有关于method_t.big的操作,而big的结构如下:

     struct big {
            SEL name;
            const char *types;
            MethodListIMP imp;
        };
    
    struct SortBySELAddress :
        public std::binary_function<const struct method_t::big&,
                                    const struct method_t::big&, bool>
        {
            bool operator() (const struct method_t::big& lhs,
                             const struct method_t::big& rhs)
            { return lhs.name < rhs.name; }
        };
    

    name是SEL类型的,所以是根据SEL进行排序,lhs.name < rhs.name
    Lhs :Left Hand Side,左边
    Rhs : Right Hand Side,右边
    所以就是从左到右由小到大排列。
    attachLists:有两个参数,List* const * addedLists, uint32_t addedCount,这是个addedLists增加addedCount个元素的操作,大致的流程是:
    -addedCount为0,不执行后面的操作直接返回;
    -如果旧的list和要新增的addedCount都不为空(many lists -> many lists),那么开始执行增加流程:
    1、记录旧list的长度,新list的长度等于旧的+ addedCount;
    2、创建一个新的array空间大小是旧array长度+ addedCount个元素大小,长度为旧array长度+ addedCount,旧数组的长度也更新为最新的长度;
    3、倒序遍历旧数组,倒序插入到新数组,然后遍历到0把新插入的addlist的内容插入到最前面。(这个地方相当于把旧数组在新数组的位置整体往后平移了addedCount个,然后再把addedLists中的数据从0开始一次添加到新数组的第addedCount,这个操作意味着,后添加进来的会在前边,有点儿像入栈操作😱)
    4、旧数组释放,新数组set。
    -旧list为空且addcount为1( 0 lists -> 1 list),直接oldList的首地址指向addlist的首地址;
    -旧list为1且addcount为大于1的时候(1 list -> many lists),oldList的一个元素直接拼接到lists的addcount位置,然后遍历addedLists赋值给lists,从0开始一直到addedCount
    这个过程有点多,我还是放一下相关的程序源码吧:

    void attachLists(List* const * addedLists, uint32_t addedCount) {
            if (addedCount == 0) return; //为0 直接返回
            if (hasArray()) {
                // many lists -> many lists  多对多
                uint32_t oldCount = array()->count;
                uint32_t newCount = oldCount + addedCount;
                array_t *newArray = (array_t *)malloc(array_t::byteSize(newCount));
                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  0到1
                list = addedLists[0];
                validate();
            } 
            else {
                // 1 list -> many lists 1到多
                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();
            }
        }
    

    hasArray()

     union {
            Ptr<List> list;
            uintptr_t arrayAndFlag;
        };
    
        bool hasArray() const {
            return arrayAndFlag & 1;
        }
    

    hasArray()是arrayAndFlag与1的一个操作,而arrayAndFlag的值是在setArray中进行的,所以setArray之后不释放就是真,没set之前就是假false。

    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);
        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->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){
                    // constant caches have been dealt with in prepareMethodLists
                    // if the class still is constant here, it's fine to keep
                    return !c->cache.isConstantOptimizedCache();
                });
            }
        }
    
        rwe->properties.attachLists(proplists + ATTACH_BUFSIZ - propcount, propcount);
    
        rwe->protocols.attachLists(protolists + ATTACH_BUFSIZ - protocount, protocount);
    }
    

    从分类关联方法列表,协议,属性到一个类上,假如所有的类都被如期加载进来了那么分类的顺序就是加载的顺序,最旧的分类是第一个被加载的。
    这个里面分别获取了分类的method_list_t,property_list_t,protocol_list_t过程和methodizeClass差不多,只是methodizeClass是从ro里获取,而这里是从rwe获取,并且通过方法attachLists,上面我们分析过了方法attachLists,后调用的会在相应的表的最前边。
    以上是分类相关属性添加到类的相关表里的操作,接下来我们来看看分类的结构,我们添加一个分类,然后 xcrun -sdk iphonesimulator clang -rewrite-objc main.m一下,看到了category_t

    category_t
    category_t.png

    由此可以知道它的结构:
    -分类是结构体类型;
    -name分类的名字;
    -cls就是分类指向的类;
    -在类中只有一个methods,在分类中有了instance_methods与class_methods。
    -分类中是有properties的,只是分类的properties没有set和get方法。

    分类中的方法是如何被加载的

    我们前边已经分析过,最终是调用attachCategories,总结下流程就是:
    -_dyld_objc_notify_register->map_images->map_images_nolock-> read_images->realizeClassWithoutSwift->methodizeClass->attachToClass-> attachCategories

    -_dyld_objc_notify_register->load_images->loadAllCategories->load_categories_nolock-> attachCategories

    • _dyld_objc_notify_register->load_images->prepare_load_methods->realizeClassWithoutSwift->methodizeClass->attachToClass-> attachCategories
      调用示意图如下:


      分类加载示意图.jpg

    相关文章

      网友评论

          本文标题:15.iOS底层学习之类的加载分析

          本文链接:https://www.haomeiwen.com/subject/zrlliltx.html