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

2021-10-15  本文已影响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

上一篇下一篇

猜你喜欢

热点阅读