美文网首页
类的加载(中)

类的加载(中)

作者: 冼同学 | 来源:发表于2021-08-03 20:16 被阅读0次

    前言

    上一篇文章中已经分析了_object_initread_images的底层原理,最后类的加载停留在了realizeClassWithoutSwift方法,这个也是我们这篇文章重点分析的点。

    准备资料

    realizeClassWithoutSwift

    realizeClassWithoutSwift方法其实在类的加载中对rwro数据进行一系列的处理,在read_images方法中进入这个方法必须实现+load方法,使类变为非懒加载。其核心代码实现如下:

    static Class realizeClassWithoutSwift(Class cls, Class previously)
    {
        runtimeLock.assertLocked();
        class_rw_t *rw;      //rw数据
        Class supercls;      //父类
        Class metacls;       //元类
        //省略cls的初始化判断
        ......
        //省略生成rw数据的逻辑
        ......
        //初始化cache(缓存)
        ......
        cls->cache.initializeToEmptyOrPreoptimizedInDisguise();
       #if FAST_CACHE_META
        //判断是否为元类
        if (isMeta) cls->cache.setBit(FAST_CACHE_META);
    #endif
        //为32位架构设计的,为isa是否纯指针做的处理。
        cls->chooseClassArrayIndex();
        ......
        //递归获取元类和父类(关联元类和父类)
        supercls = realizeClassWithoutSwift(remapClass(cls->getSuperclass()), nil);
        metacls = realizeClassWithoutSwift(remapClass(cls->ISA()), nil);
        ......
       //调整ivars
       if (supercls  &&  !isMeta) reconcileInstanceVariables(cls, supercls, ro);
        ......
       //同步flags标志位
       if ((ro->flags & RO_FORBIDS_ASSOCIATED_OBJECTS) ||
            (supercls && supercls->forbidsAssociatedObjects()))
        {
            rw->flags |= RW_FORBIDS_ASSOCIATED_OBJECTS;
        }
        ......
        //关联子类和相邻类
        if (supercls) {
            addSubclass(supercls, cls);
        } else {
            addRootClass(cls);
        }
        // Attach categories
        //分类处理
        methodizeClass(cls, previously);
    
        return cls;
    }
    

    realizeClassWithoutSwift主要做了以下的操作:

    • 利用ro数据生成rw数据。
    • 通过isa的判断处理,关联cls的父类和元类,这里会递归父类元类realizeClassWithoutSwift,最后与cls关联。
    • 调整ivarsoffset
    • 同步flags标志位给rw
    • 关联子类相邻的类。
    • 分类的处理,在methodizeClass方法中。(下面单独分析)

    rw数据的生成

     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数据空间
            rw = objc::zalloc<class_rw_t>();
            //将ro数据写进rw数据中
            rw->set_ro(ro);
            //设置flag标志位
            rw->flags = RW_REALIZED|RW_REALIZING|isMeta;
            //设置rw数据
            cls->setData(rw);
        }
    
    • 通过cls->data()->ro()读出ro数据,在之前的class结构探索的时候data()获取的是rw数据。这是因为rw还没有赋值,从macho中读取__objc_classlist就存在了data()中了。具体可以在赋值前后验证cls->data()的地址。
    • rw开辟了数据空间,将新的ro数据连接到rw中的ro()中。
    • 设置rwflags是一个uint32_t类型,31位表示rw是否已经初始化完毕(RW_REALIZED),19位表示rw是否初始化中(RW_REALIZING),0位表示是否是元类元类为1,非元类为0)。
    • 将新的rw数据放在data()中。

    注意:ro数据是在llvm编译期间已经生成。

    cls关联元类与父类

    //递归获取元类和父类
        supercls = realizeClassWithoutSwift(remapClass(cls->getSuperclass()), nil);
        metacls = realizeClassWithoutSwift(remapClass(cls->ISA()), nil);
    
    #if SUPPORT_NONPOINTER_ISA
        if (isMeta) {
            //元类的isa是纯isa
            cls->setInstancesRequireRawIsa();
        } else {
            bool instancesRequireRawIsa = cls->instancesRequireRawIsa();
            bool rawIsaIsInherited = false;
            static bool hackedDispatch = false;
    
            if (DisableNonpointerIsa) {
                // Non-pointer isa disabled by environment or app SDK version
                //设置环境变量,此时isa为纯isa
                instancesRequireRawIsa = true;
            }
            //OS_object类时纯指针
            else if (!hackedDispatch  &&  0 == strcmp(ro->getName(), "OS_object"))
            {
                // hack for libdispatch et al - isa also acts as vtable pointer
                hackedDispatch = true;
                instancesRequireRawIsa = true;
            }
            //父类是纯指针,并且父类还有父类。那么自己也要是纯指针。
            //rawIsaIsInherited 表示继承的是纯指针
            else if (supercls  &&  supercls->getSuperclass()  &&
                     supercls->instancesRequireRawIsa())
            {
                instancesRequireRawIsa = true;
                rawIsaIsInherited = true;
            }
            //递归设置父类,子类为纯isa、指针
            if (instancesRequireRawIsa) {
                cls->setInstancesRequireRawIsaRecursively(rawIsaIsInherited);
            }
        }
    // SUPPORT_NONPOINTER_ISA
    #endif
    
        // Update superclass and metaclass in case of remapping
        //关联父类和元类,也是继承链与isa走位
        cls->setSuperclass(supercls);
        cls->initClassIsa(metacls);
    
    • 递归实例化元类父类
    • 判断设置isa是否为纯isa
      • 元类的isa是纯isa
      • 类的isa是否为纯isa取决于flag的第13标志位。
      • OS_object是纯指针。
      • 递归设置isa为纯指针,子类也设置为纯指针。(父类为纯指针,子类也为纯指针)。
    • 关联父类元类也是根据isa走位和继承链

    调整ivar偏移(offset)

      //调整ivar 的offset 可能会重新创建`class_ro_t`来更新ivar
        if (supercls  &&  !isMeta) reconcileInstanceVariables(cls, supercls, ro);
        //设置ivar成员占用空间大小
        cls->setInstanceSize(ro->instanceSize);
    
    • 在有父类的情况下,并且非元类会进行ivar offset的调整,具体逻辑在reconcileInstanceVariables中。
    • 重新设置成员变量的大小。逻辑在setInstanceSize中,其中有对常量的修改。(这一点会在下篇文章重点分析)

    同步flags标志位

        //拷贝ro的flags到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;
        }
    
    • 拷贝roflag放到rw中。(其实将roflag放到缓存
    • 判断是否禁止关联对象,修改对应的flag标志位。

    子类和根类的设置

      if (supercls) {
            // 关联子类
            addSubclass(supercls, cls);
        } else {
            //设置根类
            addRootClass(cls);
        }
    
    • addSubclass目标是设置父类的子类。同时也设置了子类的相邻类以及c++构造和析构的标记。同时根据父类是否isa纯指针同步给子类。
    • addRootClass设置根类,在这个流程中NSObject相邻类会被设置为nil_firstRealizedClass会被设置为NSObject

    addSubclass

    static void addSubclass(Class supercls, Class subcls)
    {
        runtimeLock.assertLocked();
    
        if (supercls  &&  subcls) {
            ASSERT(supercls->isRealized());
            ASSERT(subcls->isRealized());
            objc_debug_realized_class_generation_count++;
            //相邻的类
            subcls->data()->nextSiblingClass = supercls->data()->firstSubclass;
            //第一个子类
            supercls->data()->firstSubclass = subcls;
            //同步父类的c++析构和构造
            if (supercls->hasCxxCtor()) {
                subcls->setHasCxxCtor();
            }
    
            if (supercls->hasCxxDtor()) {
                subcls->setHasCxxDtor();
            }
    
            objc::AWZScanner::scanAddedSubClass(subcls, supercls);
            objc::RRScanner::scanAddedSubClass(subcls, supercls);
            objc::CoreScanner::scanAddedSubClass(subcls, supercls);
    
            if (!supercls->allowsPreoptCaches()) {
                subcls->setDisallowPreoptCachesRecursively(__func__);
            } else if (!supercls->allowsPreoptInlinedSels()) {
                subcls->setDisallowPreoptInlinedSelsRecursively(__func__);
            }
            //同步子类isa是否纯指针
            if (supercls->instancesRequireRawIsa()  &&  supercls->getSuperclass()) {
                subcls->setInstancesRequireRawIsaRecursively(true);
            }
        }
    }
    
    • 设置子类的相邻类nextSiblingClass)为父类的第一个子类(firstSubclass)。
    • 设置父类的第一个子类为cls
    • 同步父类的C++析构和构造函数的标记给子类。
    • 同步父类的isa指针是否为纯指针给子类。

    addRootClass

    static void addRootClass(Class cls)
    {
        runtimeLock.assertLocked();
    
        ASSERT(cls->isRealized());
    
        objc_debug_realized_class_generation_count++;
        
        //自己的相邻类设置为第一个初始化的类(nil)。第一个初始化的类设置为自己。
        cls->data()->nextSiblingClass = _firstRealizedClass;
        _firstRealizedClass = cls;
    }
    
    • NSObject的相邻类会被设置为nil_firstRealizedClass会被设置为NSObject

    methodizeClass

    上面分析的realizeClassWithoutSwift方法最后实现了methodizeClass方法,主要是添加分类的操作。代码如下:

      //添加分类
      methodizeClass(cls, previously);
    
    • 参数cls就是当前类,previously传进来是nil。

    在_read_image方法中调用:

    realizeClassWithoutSwift(cls, nil);
    

    methodizeClass核心代码如下:

    static void methodizeClass(Class cls, Class previously)
    {
        runtimeLock.assertLocked();
    
        bool isMeta = cls->isMetaClass();
        auto rw = cls->data();  //获取rw数据
        auto ro = rw->ro();     //获取rw中的ro数据
        auto rwe = rw->ext();   //获取rw中的rwe数据(可扩展)
    
        // Methodizing for the first time
        //获取ro方法列表
        if (PrintConnecting) {
            _objc_inform("CLASS: methodizing class '%s' %s", 
                         cls->nameForLogging(), isMeta ? "(meta)" : "");
        }
    
        // Install methods and properties that the class implements itself.
        //ro方法列表获取
        method_list_t *list = ro->baseMethods();
        if (list) {
            prepareMethodLists(cls, &list, 1, YES, isBundleClass(cls), nullptr);
            if (rwe) rwe->methods.attachLists(&list, 1);
        }
        //获取ro属性列表
        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.
        //是否根元类,根元类加了initialize方法
        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);
    ......(省略部分代码)
    
    • ro的方法列表修正和排序。
      • 方法的attachLists处理。(没有rwe的情况下不走)
      • 属性的attachLists处理。(没有rwe的情况下不走)
      • 协议的attachLists处理。(没有rwe的情况下不走)
    • 在根元类中添加initialize方法。
    • 分类的attachToClass处理。(最终不会走进attachCategories逻辑)

    注意:attachCategories方法在attachToClass方法里面调用。

    prepareMethodLists

    方法入口:

    prepareMethodLists(cls, &list, 1, YES, isBundleClass(cls), nullptr);
    

    此方法是获取ro的方法列表,核心代码如下:

    //cls, &list, 1, YES, isBundleClass(cls), nullptr
    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.
        //addedCount 为 1
        for (int i = 0; i < addedCount; i++) {
            //addedLists 也就是list。
            method_list_t *mlist = addedLists[i];
            ASSERT(mlist);
    
            // Fixup selectors if necessary
            //是否已经排序,没有则进行排序。对ro methodlist 排序
            if (!mlist->isFixedUp()) {
                //修正并且排序methodList
                fixupMethodList(mlist, methodsFromBundle, true/*sort*/);
            }
        }
    ……省略一部分代码
    }
    
    • addedCount值为1addedLists**(指针的地址)类型。那么mlist就是rolist
    • 如果没有排序则修正并且排序romethodLists

    mlist验证:

    mlist验证
    结论:listmlist是同一个地址,也就是他们是一样的。(通过地址偏移的方式找出list然后赋值给mlist)

    fixupMethodList

    static void 
    fixupMethodList(method_list_t *mlist, bool bundleCopy, bool sort)
    {
        //runtime的锁操作
        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) {
                //SEL方法编号转字符型的name
                const char *name = sel_cname(meth.name());
                //将name和地址设置在meth中
                ///设置SEL,SEL有可能在 __sel_registerName 最终调用了_dyld_get_objc_selector的值,相当于修正到dyld中
    //            printf("前面 -- name:%s -- address:%p\n",name,meth.name());
                meth.setName(sel_registerNameNoLock(name, bundleCopy));
    //            printf("后面 -- name:%s -- address:%p\n",name,meth.name());
            }
        }
        //排序,通过SEL的地址排序。
        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();
        }
    }
    
    • 先对method的name也就是SEL进行了修正。sel_registerNameNoLock -> __sel_registerName -> search_builtins -> _dyld_get_objc_selector。相当于以dyld的为准。
    • 接着对methodList进行了排序(按照SEL的地址),这里就与慢速消息查找的二分查找对应上了。
    • 设置排序的标志位。

    注意:small list是不可变的,也不会进行排序。

    修正前后的SEL验证:

    SEL验证
    结论:runeat的地址发生了变化,变成了dyld提供的地址了。

    排序前后的验证:

    • 排序前:


      排序前
    • 排序后:

      排序后
      明显就是自定义的方法发生了顺序的改变,系统级别的方法是没有改变的。
    • 方法的顺序默认是按照编译时候的顺序排序的,一般情况下是有序的。

    • 在进行dyld修正SEL地址后需要重新排序

    分类的探索

    根据以上的代码分析可知在prepareMethodLists执行完成后是没有rwe数据的,所以后续的attachLists相关操作都不会执行,那么我们添加一个分类如下:

    #import "XJLPerson.h"
    
    NS_ASSUME_NONNULL_BEGIN
    
    @interface XJLPerson (test)
    @property (nonatomic,strong) NSString *name_test;
    @property (nonatomic,assign) NSInteger age_test;
    -(void)test;
    -(void)sayNB;
    
    #import "XJLPerson+test.h"
    
    @implementation XJLPerson (test)
    -(void)test{
        NSLog(@"---xjl---%s",__func__);
    }
    -(void)sayNB{
        NSLog(@"---xjl---%s",__func__);
    }
    @end
    @end
    
    NS_ASSUME_NONNULL_END
    

    编译项目使用Xrun导出底层代码(main.cpp)查看分类的实现:

    static struct _category_t *L_OBJC_LABEL_CATEGORY_$ [1] __attribute__((used, section ("__DATA, __objc_catlist,regular,no_dead_strip")))= {
        &_OBJC_$_CATEGORY_XJLPerson_$_test,
    };
    

    可以看出_XJLPerson_$_test_category_t类型的。

    查看_category_t结构体数据结构:

    struct _category_t {
        const char *name;
        struct _class_t *cls;
        const struct _method_list_t *instance_methods;
        const struct _method_list_t *class_methods;
        const struct _protocol_list_t *protocols;
        const struct _prop_list_t *properties;
    };
    
    • 分类是一个结构体类型。
    • name的名字估计是test
    • cls指向
    • 在类中只有一个methods,在分类中有了instance_methodsclass_methods。因为分类没有元类(也就是没有分元类)。
    • 分类中是有properties的。

    继续观察分类底层生成信息:

    static struct _category_t _OBJC_$_CATEGORY_XJLPerson_$_test __attribute__ ((used, section ("__DATA,__objc_const"))) = 
    {
        "XJLPerson",//名字
        0, // &OBJC_CLASS_$_XJLPerson,//cls
        (const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_XJLPerson_$_test,//实例方法
        (const struct _method_list_t *)&_OBJC_$_CATEGORY_CLASS_METHODS_XJLPerson_$_test,//类方法
        0,///协议
        (const struct _prop_list_t *)&_OBJC_$_PROP_LIST_XJLPerson_$_test,//属性
    };
    
    • name赋值了XJLPerson而不是test,因为静态编译的时候还不知道名字,所以拿类的名称来赋值。
    • cls没有赋值,但是有注释。这个时候还没有关联,需要运行时关联
    • 协议也没有赋值。

    查看分类的属性情况:

    static struct /*_prop_list_t*/ {
        unsigned int entsize;  // sizeof(struct _prop_t)
        unsigned int count_of_properties;
        struct _prop_t prop_list[2];
    } _OBJC_$_PROP_LIST_XJLPeron_$_test __attribute__ ((used, section ("__DATA,__objc_const"))) = {
        sizeof(_prop_t),
        2,
        {{"name_test","T@\"NSString\",C,N"},
        {"age_test","Ti,N"}}
    };
    

    此时属性已经有了,但是通过之后通过方法生成的地方查找不到属性的settergetter方法,这只能通过关联对象进行处理哦。

    分类加载源码的探究

    通过上面的分析,大概了解了分类的结构。分类本身是一个结构体,那么它是怎么加载的呢?通过类的加载源码的分析核心逻辑在attachListsattachToClass中。控制条件是rwe

    auto rwe = rw->ext();
    

    进入ext()方法查看核心代码:

    class_rw_ext_t *ext() const {
        return get_ro_or_rwe().dyn_cast<class_rw_ext_t *>(&ro_or_rw_ext);
    }
    class_rw_ext_t *extAllocIfNeeded() {
        //获取rwe
        auto v = get_ro_or_rwe();
        if (fastpath(v.is<class_rw_ext_t *>())) {
            return v.get<class_rw_ext_t *>(&ro_or_rw_ext);
        } else {
            //创建rwe
            return extAlloc(v.get<const class_ro_t *>(&ro_or_rw_ext));
        }
    }
    

    extAllocIfNeeded中进行了rwe的创建。extAllocIfNeeded的调用分为以下情况(rwe创建情况):

    extAllocIfNeeded调用位置
    • attachCategories
    • demangledNameisRealized()或者isFuture()
    • class_setVersion类的版本设置。
    • addMethods_finish
    • class_addProtocol
    • _class_addProperty
    • objc_duplicateClass

    可以看到除了attachCategories,其它要么是对类进行动态处理要么是修复类的时候创建rwe。这与WWDC上的介绍就相吻合了。那么显然核心逻辑就在attachCategories了。

    但是attachCategories的调用逻辑在attachToClassload_categories_nolock中。

    • attachToClass是在methodizeClass中调用的
    • load_categories_nolock是在_read_images(这里不会调用)与loadAllCategories中。
    • loadAllCategories是在load_images的时候加载。

    那么我们就能得出分类的加载只有以下的两条线路:
    1. methodizeClass -> attachToClass -> attachCategories
    2. load_images -> loadAllCategories -> load_categories_nolock -> attachCategories

    注意:分类的加载在下一篇文章会进行详细的分析哦。

    总结

    realizeClassWithoutSwift

    • 通过ro生成rw数据。这里rw只是关联到了ro
    • isa判断处理,关联元类父类
      • 元类的isa指针是纯isa指针。
      • 类的isa指针是通过递归进行设置的,父类的isa指针为纯isa指针的话,它的子类的isa指针也为纯isa指针
    • 调整ivarsoffset
    • 同步flags的标志位给rw。(其实是将ro数据读到了缓存中)
    • 关联子类(firstSubclass)与相邻的类(nextSiblingClass)。

    methodizeClass
    主要是对方法列表进行排序 &加载分类 & rwe 的处理。

    • prepareMethodLists
      -fixupMethodList 修正并且排序方法列表(ro的)
      • sel_registerNameNoLock最终执行_dyld_get_objc_selectorSEL地址修复为dyld提供的。
      • SortBySELAddress对方法列表进行排序。
    • attachToClass分类的加载。

    rwe()方法

    • rwe是在extAllocIfNeeded中创建的。
      • 加载分类。
      • 动态修改类(addMethods_finishclass_addProtocol_class_addProperty)。
    • 修复类(demangledNameclass_setVersionobjc_duplicateClass)。

    分类加载的两条线路:
    1. methodizeClass -> attachToClass -> attachCategories
    2. load_images -> loadAllCategories -> load_categories_nolock -> attachCategories

    相关文章

      网友评论

          本文标题:类的加载(中)

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