美文网首页iOS底层
Objective-C 类的加载原理(中)

Objective-C 类的加载原理(中)

作者: HotPotCat | 来源:发表于2021-07-16 18:43 被阅读0次

    上篇文章分析了 _objc_init与read_images 的逻辑,最后定位到了类的初始化是在realizeClassWithoutSwift中的,这篇文章将继续分析。

    一、 realizeClassWithoutSwift

    realizeClassWithoutSwift中发现了对rorw等的一系列操作。在read_imags中要进入这个方法需要实现+ load方法。
    核心逻辑精简后如下:

    static Class realizeClassWithoutSwift(Class cls, Class previously)
    {
        class_rw_t *rw;
        Class supercls;
        Class metacls;
    
    ……
    1.生成rw数据逻辑
        //cache的初始化
        cls->cache.initializeToEmptyOrPreoptimizedInDisguise();
    
    
    #if FAST_CACHE_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
        //为32位设计的。objc_indexed_classes_count 记录类的数量 最终存入objc_indexed_classes中(其中记录了index-cls的关系)。为isa是否纯指针做的处理。
        cls->chooseClassArrayIndex();
    2.关联元类与父类
        
    
    3.调整ivars
    
    4.同步flags标志位
    
    5.关联子类与相邻类
    
        // Attach categories
        //分类处理
        methodizeClass(cls, previously);
    
        return cls;
    }
    
    • 通过ro生成rw数据。
    • isa的判断处理,关联元类与父类。这里会递归父类和元类的realizeClassWithoutSwift,最后与cls关联。
    • 调整ivarsoffset
    • 同步flags的标志位给rw(实际上是从ro中存储到缓存中)。
    • 关联子类与相邻的类。
    • 分类的处理逻辑在methodizeClass中,将单独分析。

    1.1、生成rw数据

    auto ro = (const class_ro_t *)cls->data();
    auto isMeta = ro->flags & RO_META;
    
    //调试代码 开始
    const char *mangledName = cls->nonlazyMangledName();
    if (strcmp(mangledName, "HPObject") == 0 && !isMeta) {
        printf("%s %s\n",__func__,mangledName);
    }
    //调试代码 结束
    
    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中。这也是为什么从data()->ro()的原因。
        rw->set_ro(ro);
        //flags标志位设置 uint32_t 1<<31 1<<19 1<<0 (元类为1,非元类为0)
        rw->flags = RW_REALIZED|RW_REALIZING|isMeta;
        //设置rw数据,这个时候data()拿出来的就是rw数据了。
        cls->setData(rw);
    }
    
    • 首先通过cls->data()获取ro数据,在之前的class结构探索的时候data()获取的是rw数据。这是因为rw还没有赋值,从macho中读取__objc_classlist就存在了data()中了。具体可以在赋值前后验证cls->data()的地址。
    • rw开辟空间,将ro链接到rwro()中。(这里只是将地址给了rw,并没有完全复制一份,苹果对这块做了对应的优化。具体可以参考OC 类探索(二) 或者观看WWDC2020-10163)
    • 设置rwflags是一个uint32_t类型,31位表示rw是否已经初始化完毕(RW_REALIZED),19位表示rw是否初始化中(RW_REALIZING),0位表示是否是元类(元类为1,非元类为0)。
    • 最后将rw存进data中。

    ⚠️这里有个细节是调试代码中判断了isMeta,因为元类和类的名称相同。否则元类也会进入这块逻辑。
    ro数据是在llvm编译期就已经生成了。class_ro_tllvm中的结构:

    image.png
    read函数实现:
    image.png
    可以看到是直接进行的macho文件的操作,直接进行的赋值。调用方是Read_class_row
    image.png

    验证ro数据(类加载进内存就有了):

    (lldb) p ro
    (const class_ro_t *) $0 = 0x00000001000080a8
    (lldb) p *$0
    (const class_ro_t) $1 = {
      flags = 128
      instanceStart = 8
      instanceSize = 8
      reserved = 0
       = {
        ivarLayout = 0x0000000000000000
        nonMetaclass = nil
      }
      name = {
        std::__1::atomic<const char *> = "HPObject" {
          Value = 0x0000000100003f2e "HPObject"
        }
      }
      baseMethodList = 0x00000001000080f0
      baseProtocols = 0x0000000000000000
      ivars = 0x0000000000000000
      weakIvarLayout = 0x0000000000000000
      baseProperties = 0x0000000000000000
      _swiftMetadataInitializer_NEVER_USE = {}
    }
    (lldb) p $1.baseMethodList
    (void *const) $2 = 0x00000001000080f0
    (lldb) p *$2
    (lldb) p $1.baseMethods()
    (method_list_t *) $3 = 0x00000001000080f0
    (lldb) p *$3
    (method_list_t) $4 = {
      entsize_list_tt<method_t, method_list_t, 4294901763, method_t::pointer_modifier> = (entsizeAndFlags = 24, count = 1)
    }
    (lldb) p $4.get(0).big()
    (method_t::big) $5 = {
      name = "instanceMethod"
      types = 0x0000000100003f57 "v16@0:8"
      imp = 0x0000000100003ea0 (HPObjcTest`-[HPObject instanceMethod])
    }
    
    • 通过打印ro地址定位到了baseMethodList
    • 打印baseMethodList并没有显示任何数据,可以通过baseMethods()方法或者将baseMethodList强转成method_list_t *来打印。
    • 最后可以通过get(0).big()获取到方法的详细信息。

    验证data数据:

    image.png
    • 刚开始存储的是ro数据,生成rw数据后就存储rw数据了,ro数据存储在rw数据中。

    验证一般情况下rw的方法列表指向ro的方法列表:

    image.png

    1.2 cls关联元类与父类

        //父类和元类的实例化
        supercls = realizeClassWithoutSwift(remapClass(cls->getSuperclass()), nil);
        metacls = realizeClassWithoutSwift(remapClass(cls->ISA()), nil);
    
    #if SUPPORT_NONPOINTER_ISA
        if (isMeta) {
            //元类isa是纯指针。
            cls->setInstancesRequireRawIsa();
        } else {
            //isa是否纯指针, flags中第13位
            bool instancesRequireRawIsa = cls->instancesRequireRawIsa();
            bool rawIsaIsInherited = false;
            static bool hackedDispatch = false;
            //这就是环境变量中配置的 OBJC_DISABLE_NONPOINTER_ISA
            if (DisableNonpointerIsa) {
                // Non-pointer isa disabled by environment or app SDK version
                //配置环境变量为YES后,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为纯指针,子类也设置为纯指针。(父类为纯指针,子类也为纯指针)。rawIsaIsInherited只是控制打印。
            if (instancesRequireRawIsa) {
                cls->setInstancesRequireRawIsaRecursively(rawIsaIsInherited);
            }
        }
    // SUPPORT_NONPOINTER_ISA
    #endif
    
        //关联父类与元类。也就是继承链与isa走位。
        cls->setSuperclass(supercls);
        cls->initClassIsa(metacls);
    
    • 递归实例化父类和元类。
    • 判断设置isa是否纯指针。
      • 元类isa是纯指针。
      • 类的isa是否纯指针取值flags第13位。
      • DisableNonpointerIsa也就是OBJC_DISABLE_NONPOINTER_ISA环境变量配置来配置是否纯指针。
      • OS_object是纯指针。
      • 父类是纯指针,并且父类还有父类。那么自己也要是纯指针。rawIsaIsInherited (只是控制打印)表示继承的是纯指针。
      • 递归设置isa为纯指针,子类也设置为纯指针。(父类为纯指针,子类也为纯指针)。
    • 关联父类与元类,也就是继承链与isa走位。

    1.3 调整ivar offset

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

    对于成员变量的解读,将放在下一篇文章。这篇文章的重点是方法的加载。

    1.4 rw同步ro标志位

    //拷贝ro的flags到rw中  flags第2位 c++构造方法 RO_HAS_CXX_STRUCTORS,flags第8位 RO_HAS_CXX_DTOR_ONLY(c++析构方法)
    if (ro->flags & RO_HAS_CXX_STRUCTORS) {
        cls->setHasCxxDtor();
        if (! (ro->flags & RO_HAS_CXX_DTOR_ONLY)) {
            cls->setHasCxxCtor();
        }
    }
    
    //是否禁止关联对象 第20位标记是否允许关联对象。
    if ((ro->flags & RO_FORBIDS_ASSOCIATED_OBJECTS) ||
        (supercls && supercls->forbidsAssociatedObjects()))
    {
        rw->flags |= RW_FORBIDS_ASSOCIATED_OBJECTS;
    }
    
    • 拷贝roflagsrw中(其实是放在缓存中)。
    • RO_HAS_CXX_STRUCTORS flags2位标记c++构造方法。
    • RO_HAS_CXX_DTOR_ONLY flags8位标记c++析构方法。
    • 自己禁止关联对象或者父类禁止关联对象(父类禁止,子类肯定禁止),则同步RW_FORBIDS_ASSOCIATED_OBJECTS标记。flags20位标记是否禁止关联对象。

    1.5 子类与根类的设置

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

    1.5.1 addSubclass

    static void addSubclass(Class supercls, Class subcls)
    {
    ……
        if (supercls  &&  subcls) {
    ……
    
            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();
            }
            
    ……
            //同步子类isa是否纯指针
            if (supercls->instancesRequireRawIsa()  &&  supercls->getSuperclass()) {
                subcls->setInstancesRequireRawIsaRecursively(true);
            }
        }
    }
    
    • 设置子类的相邻类(nextSiblingClass)为父类的第一个子类(firstSubclass)。
    • 设置父类的第一个子类为cls
    • 同步设置父类的c++析构和构造函数的标记给子类。
    • 同步父类的isa是否纯指针给子类。

    1.5.2 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

    但是经过断点调试发现这个方法会进入两次:


    image.png

    第一次符合预期。第二次是__NSAtom进入的。它的相邻类是NSObject_firstRealizedClass变成了__NSAtom。堆栈是在+ load方法后:

    image.png

    那么__NSAtom是什么呢?根据上面的逻辑__NSAtom的相邻类为NSObject,第一个初始化的类变成了__NSAtom。在objc源码中并没有搜到相关类,只有OBJC_TAG_NSAtom。在class-dumpCoreFoundationCoreFoundation源码中也没有开源这一部分)头文件中发下了如下声明:

    @interface __NSAtom : _UKNOWN_SUPERCLASS_ {
    
        Class isa;
    
    }
    +(void)initialize;
    @end
    

    那就证明__NSAtom是属于CoreFoundation的。目前暂不清楚这个类的用法以及作用。
    参考

    二、methodizeClass

    realizeClassWithoutSwift中最后调用了如下代码:

    // Attach categories
    methodizeClass(cls, previously);
    

    根据注释可以看到应该是对分类的处理,参数cls没问题,previously是从_read_images中传过来的为nil

    realizeClassWithoutSwift(cls, nil);
    

    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();
    
       ……
        //获取ro方法列表
        method_list_t *list = ro->baseMethods();
        if (list) {
            //RO_FROM_BUNDLE 29 位标记是否bundleclass
            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.
        objc::unattachedCategories.attachToClass(cls, cls,
                                                 isMeta ? ATTACH_METACLASS : ATTACH_CLASS);
       ……
    }
    
    • ro的方法列表修正和排序。
    • 方法的attachLists处理。(没有rwe的情况下不走)
    • 属性的attachLists处理。(没有rwe的情况下不走)
    • 协议的attachLists处理。(没有rwe的情况下不走)
    • 根元类添加initialize方法。
    • 分类的attachToClass处理。(最终不会走进attachCategories逻辑)

    2.1 prepareMethodLists

    //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*/);
            }
        }
    ……
    }
    
    • RO_FROM_BUNDLE flags29位标记是否bundleclass
    • addedCount值为1addedLists**类型。那么mlist就是rolist
    • 如果没有排序则修正并且排序romethodLists

    验证mlist

    image.png

    2.1.1 fixupMethodList

    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) {
                //SEL转成name字符串
                const char *name = sel_cname(meth.name());
                //将name和地址设置进meth中
                printf("before setName:%s address:%p\n",name,meth.name());
                //设置SEL,SEL有可能在 __sel_registerName 最终调用了_dyld_get_objc_selector的值,相当于修正到dyld中。
                meth.setName(sel_registerNameNoLock(name, bundleCopy));
                printf("after setName:%s address:%p\n",name,meth.name());
            }
        }
    
        // Sort by selector address.
        // Don't try to sort small lists, as they're immutable.
        // Don't try to sort big lists of nonstandard size, as stable_sort
        // won't copy the entries properly.
        //排序,通过SEL的地址排序。samll lists 不可变,不排序。这里就与慢速消息查找的二分查找对应上了。在这里进行的排序。
        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();
        }
    }
    
    • 先对methodname也就是SEL进行了修正。sel_registerNameNoLock -> __sel_registerName -> search_builtins->_dyld_get_objc_selector。相当于以dyld的为准。
    • 接着对methodList进行了排序(按照SEL的地址),这里就与慢速消息查找的二分查找对应上了。排序是在这里进行的。
    • 最后设置标志位。

    small lists不可变不进行排序。

    修正前后SEL验证:

    image.png
    发现run的地址发生了变化,修正为dyld提供的地址了。

    添加log进行排序前后验证:

    image.png
    其它方法顺序没有发生变化,run发生了变化。根据以上验证可以得出以下结论:
    • 方法的顺序默认是按照编译时候的顺序排序的,一般情况下是有序的。(文件中方法的顺序)
    • 在进行dyld修正SEL地址后需要重新排序。

    2.2 分类的探索

    prepareMethodLists执行完成后是没有rwe数据的,所以后续的attachLists相关操作都不会执行。根据之前WWDC的介绍rwe在有分类的情况下会出现,那么就加个分类:

    @interface HPObject (HP)
    
    @property (nonatomic, copy) NSString *hp_name;
    @property (nonatomic, assign) int hp_age;
    
    - (void)hp_instanceMethod1;
    
    - (void)hp_instanceMethod2;
    
    + (void)hp_classMethod1;
    
    @end
    
    @implementation HPObject (HP)
    
    - (void)hp_instanceMethod1 {
        NSLog(@"%s",__func__);
    }
    
    - (void)hp_instanceMethod2 {
        NSLog(@"%s",__func__);
    }
    
    + (void)hp_classMethod1 {
        NSLog(@"%s",__func__);
    }
    
    @end
    

    2.2.1 clang还原底层代码

    既然要研究分类的实现,不防先探索下底层的实现。
    在转换后的cpp文件中有如下实现:

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

    _CATEGORY_HPObject_$_HP是一个_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的名字应该是HP
    • cls指向类。
    • 在类中只有一个methods,在分类中有了instance_methodsclass_methods。因为分类没有元类(也就是没有分元类)。
    • 分类中是有properties的。

    接着有以下代码:

    static struct _category_t _OBJC_$_CATEGORY_HPObject_$_HP __attribute__ ((used, section ("__DATA,__objc_const"))) = 
    {
        "HPObject",//名字
        0, // &OBJC_CLASS_$_HPObject,//cls
        (const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_HPObject_$_HP,//实例方法
        (const struct _method_list_t *)&_OBJC_$_CATEGORY_CLASS_METHODS_HPObject_$_HP,//类方法
        0,///协议
        (const struct _prop_list_t *)&_OBJC_$_PROP_LIST_HPObject_$_HP,//属性
    };
    
    • 这里name给了HPObject而不是HP,因为静态编译的时候还不知道名字。只是做了赋值。
    • cls没有赋值,但是有注释。这个时候还没有关联,需要运行时关联。
    • 协议也没有赋值。

    直接遵循NSObject协议再重新编译下:

    image.png
    static struct /*_protocol_list_t*/ {
        long protocol_count;  // Note, this is 32/64 bit
        struct _protocol_t *super_protocols[1];
    } _OBJC_CATEGORY_PROTOCOLS_$_HPObject_$_HP __attribute__ ((used, section ("__DATA,__objc_const"))) = {
        1,
        &_OBJC_PROTOCOL_NSObject
    };
    

    这个时候协议就有值了,正是NSObject协议。

    这个时候属性确实已经有了:

    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_HPObject_$_HP __attribute__ ((used, section ("__DATA,__objc_const"))) = {
        sizeof(_prop_t),
        2,
        {{"hp_name","T@\"NSString\",C,N"},
        {"hp_age","Ti,N"}}
    };
    

    但是并没有属性队形的set & get

    static struct /*_method_list_t*/ {
        unsigned int entsize;  // sizeof(struct _objc_method)
        unsigned int method_count;
        struct _objc_method method_list[2];
    } _OBJC_$_CATEGORY_INSTANCE_METHODS_HPObject_$_HP __attribute__ ((used, section ("__DATA,__objc_const"))) = {
        sizeof(_objc_method),
        2,
        {{(struct objc_selector *)"hp_instanceMethod1", "v16@0:8", (void *)_I_HPObject_HP_hp_instanceMethod1},
        {(struct objc_selector *)"hp_instanceMethod2", "v16@0:8", (void *)_I_HPObject_HP_hp_instanceMethod2}}
    };
    
    static struct /*_method_list_t*/ {
        unsigned int entsize;  // sizeof(struct _objc_method)
        unsigned int method_count;
        struct _objc_method method_list[1];
    } _OBJC_$_CATEGORY_CLASS_METHODS_HPObject_$_HP __attribute__ ((used, section ("__DATA,__objc_const"))) = {
        sizeof(_objc_method),
        1,
        {{(struct objc_selector *)"hp_classMethod1", "v16@0:8", (void *)_C_HPObject_HP_hp_classMethod1}}
    };
    

    所以只能通过关联对象处理。

    2.2.2 category_t 源码验证

    struct category_t {
        const char *name;
        classref_t cls;
        WrappedPtr<method_list_t, PtrauthStrip> instanceMethods;
        WrappedPtr<method_list_t, PtrauthStrip> classMethods;
        struct protocol_list_t *protocols;
        struct property_list_t *instanceProperties;
        // Fields below this point are not always present on disk.
        struct property_list_t *_classProperties;
    
        method_list_t *methodsForMeta(bool isMeta) {
            if (isMeta) return classMethods;
            else return instanceMethods;
        }
    
        property_list_t *propertiesForMeta(bool isMeta, struct header_info *hi);
        
        protocol_list_t *protocolsForMeta(bool isMeta) {
            if (isMeta) return nullptr;
            else return protocols;
        }
    };
    
    • 在源码中category_tclang转换的c++代码结构不同。
    • 不仅类型不同,并且还有一个_classProperties

    2.2.3 文档探索

    Xcode文档中搜索Category得到以下内容:

    //An opaque type that represents a category.
    typedef struct objc_category *Category;
    

    在源码中搜索objc_category发现OBJC2_UNAVAILABLE

    struct objc_category {
        char * _Nonnull category_name                            OBJC2_UNAVAILABLE;
        char * _Nonnull class_name                               OBJC2_UNAVAILABLE;
        struct objc_method_list * _Nullable instance_methods     OBJC2_UNAVAILABLE;
        struct objc_method_list * _Nullable class_methods        OBJC2_UNAVAILABLE;
        struct objc_protocol_list * _Nullable protocols          OBJC2_UNAVAILABLE;
    }                                                            OBJC2_UNAVAILABLE;
    

    2.2.4 分类加载源码探究

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

    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创建情况):

    • 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是递归设置的,父类为纯指针,子类也为纯指针。
      • 调整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)。
    • 分类的加载有两条路径:
      • methodizeClass -> attachToClass -> attachCategories
      • load_images -> loadAllCategories -> load_categories_nolock -> attachCategories

    相关文章

      网友评论

        本文标题:Objective-C 类的加载原理(中)

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