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

类的加载原理(中)

作者: 浅墨入画 | 来源:发表于2021-08-13 18:47 被阅读0次

    realizeClassWithoutSwift引入

    如何找到执行class的rw、ro、rwe操作入口
    • 通过最直接的方式:设置断点+写入打印代码,动态调试
    • 只关心自定义的class(LGPerson操作),查看测试代码执行情况
    • _read_images内所有关于class操作的地方进行监测,Realize non-lazy classes、realize future classes
    <!-- 实现非懒加载的类 -->
    // 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;
    
    <!-- 插入的测试代码 -->
                // KC 专用
                const char *mangledName = cls->nonlazyMangledName();
                const char *LGPersonName = "LGPerson";
                // 节约内存 速度
                if (strcmp(mangledName, LGPersonName) == 0) {
                    // 普通写得类 他是如何
                    printf("%s Realize non-lazy classes -KC: 要研究的: - %s\n",__func__,mangledName);
                }
    <!-- 插入的测试代码 -->
                // KC 专用
                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);
            }
        }
    
        ts.log("IMAGE TIMES: realize non-lazy classes");
    
    <!-- 实现新解析的未来类 -->
        // Realize newly-resolved future classes, in case CF manipulates them
        if (resolvedFutureClasses) {
            for (i = 0; i < resolvedFutureClassCount; i++) {
                Class cls = resolvedFutureClasses[i];
    
    <!-- 插入的测试代码 -->
                // KC 专用
                const char *mangledName = cls->nonlazyMangledName();
                const char *LGPersonName = "LGPerson";
                if (strcmp(mangledName, LGPersonName) == 0) {
                    // 普通写得类 他是如何
                    printf("%s -resolvedFutureClasses-KC: 要研究的: - %s\n",__func__,mangledName);
                }
    <!-- 插入的测试代码 -->
                // KC 专用
                if (cls->isSwiftStable()) {
                    _objc_fatal("Swift class is not allowed to be future");
                }
                realizeClassWithoutSwift(cls, nil);
                cls->setInstancesRequireRawIsaRecursively(false/*inherited*/);
            }
            free(resolvedFutureClasses);
        }
    
        ts.log("IMAGE TIMES: realize future classes");
    
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            NSLog(@"Hello, World!");
            LGPerson *p = [LGPerson alloc];
        }
    }
    
    // 运行工程打印如下,并没有执行到插入的测试代码中
    2021-08-14 10:46:59.302509+0800 KCObjcBuild[32336:3938795] Hello, World!
    Program ended with exit code: 0
    
    <-- 调整案例代码 -->
    @implementation LGPerson
    + (void)load{
        NSLog(@"load");
    }
    @end
    
    // 运行工程打印如下,执行到插入的测试代码中
    _read_images Realize non-lazy classes -KC: 要研究的: - LGPerson
    2021-08-14 11:00:29.035752+0800 KCObjcBuild[32420:3947246] load
    2021-08-14 11:00:29.036473+0800 KCObjcBuild[32420:3947246] Hello, World!
    Program ended with exit code: 0
    

    找到的入口在Realize non-lazy classes,然后向下执行的是realizeClassWithoutSwift函数

    realizeClassWithoutSwift分析

    realizeClassWithoutSwift方法中有ro、rw的相关操作。这个方法在消息慢速查找流程中有所提及。其主要作用是实现类,将类的data数据加载到内存中。主要步骤如下

    • 类的初始化
    • 读取data数据,并设置ro、rw
    • 递归调用realizeClassWithoutSwift完善继承链
    • 通过methodizeClass方法化类
    类的初始化
        if (!cls) return nil;
        if (cls->isRealized()) {
            validateAlreadyRealizedClass(cls);
            return cls;
        }
    

    首先判断类是否存在,如果不存在直接返回nil,然后再判断这个类是否已经验证实现,如果实现则返回当前类,主要是因为这个地方也存在递归实现元类与父类,根类的父类为nil、元类的isa指向自己,所以这样可以保证类只会被初始化一次

    读取data数据,并设置ro、rw
    // fixme verify class is not in an un-dlopened part of the shared cache?
        
        // 获取类中的RO数据
        auto ro = (const class_ro_t *)cls->data();
        auto isMeta = ro->flags & RO_META;
        if (ro->flags & RO_FUTURE) {
            // 元类:rw数据已经写入完成
            rw = cls->data();
            ro = cls->data()->ro();
            ASSERT(!isMeta);
            cls->changeInfo(RW_REALIZED|RW_REALIZING, RW_FUTURE);
        } else {
            //类:1、开辟rw空间
            rw = objc::zalloc<class_rw_t>();
            //2、将ro写入rw
            rw->set_ro(ro);
            //3、flags标志位设置,元类为1,非元类为0
            rw->flags = RW_REALIZED|RW_REALIZING|isMeta;
            //4、设置rw数据
            cls->setData(rw);
        }
    

    读取class的data数据,并将其强转为rorw的初始化,ro拷贝一份到rw中的ro

    • ro:clean memory,在编译期间确定的内存空间,只读不可改变,其存储着类名称、属性、协议、方法、实例变量等
    • rw: dirty memory,在 运行时 生成,可读可写,由于其动态性,可以往类中添加属性、方法、协议
    • rwe :类的额外信息,只有不到10%的类真正的更改了他们的方法,并不是每一个类都需要插入数据,进行修改的类很少,避免资源的消耗,所以就有了rwe。rwe中存储的一般是分类的信息、动态添加的方法等
    递归调用realizeClassWithoutSwift完善继承链
        //递归调用realizeClassWithoutSwift完善继承链,并处理当前类的父类、元类
        //递归实现 设置当前类、父类、元类的 rw,主要目的是确定继承链 (类继承链、元类继承链)
        //实现元类、父类
        //当isa找到根元类之后,根元类的isa是指向自己的,不会返回nil从而导致死循环——remapClass中对类在表中进行查找的操作,如果表中已有该类,则返回一个空值;如果没有则返回当前类,这样保证了类只加载一次并结束递归
        supercls = realizeClassWithoutSwift(remapClass(cls->superclass), nil);
        metacls = realizeClassWithoutSwift(remapClass(cls->ISA()), nil);
        
    ...
    
    // Update superclass and metaclass in case of remapping -- class 是 双向链表结构 即父子关系都确认了
    // 将父类和元类给我们的类 分别是isa和父类的对应值
    cls->superclass = supercls;
    cls->initClassIsa(metacls);
    
    ...
    
    // Connect this class to its superclass's subclass lists
    //双向链表指向关系 父类中可以找到子类 子类中也可以找到父类
    //通过addSubclass把当前类放到父类的子类列表中去
    if (supercls) {
        addSubclass(supercls, cls);
    } else {
        addRootClass(cls);
    }
    

    递归调用realizeClassWithoutSwift完善继承链,并设置当前类、父类、元类的rw

    • 递归调用realizeClassWithoutSwift并设置父类、元类
    • 设置父类和元类的isa指向
    • 通过addSubclassaddRootClass设置父子的双向链表指向关系,即父类中可以找到子类,子类中可以找到父类
    通过methodizeClass方法化类
    // Attach categories 附加类别 -- 疑问:ro中也有方法列表 rw中也有方法列表,下面这个方法可以说明
        // 将ro数据写入到rw
        methodizeClass(cls, previously);
    
        return cls;
    

    通过methodizeClass方法,从ro中读取方法列表(包括分类中的方法)、属性列表协议列表 赋值给rw并返回cls

    methodizeClass分析

    methodizeClass源码实现如下,主要分以下几部分

    • 属性列表、方法列表、协议列表等贴到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();
        ......
    
        // Install methods and properties that the class implements itself.
        //将属性列表、方法列表、协议列表等贴到rwe中
        // 将ro中的方法列表加入到rwe中
        method_list_t *list = ro->baseMethods();
        if (list) {
            prepareMethodLists(cls, &list, 1, YES, isBundleClass(cls), nullptr);
            if (rwe) rwe->methods.attachLists(&list, 1);
        }
        //将属性添加到rwe中
        property_list_t *proplist = ro->baseProperties;
        if (rwe && proplist) {
            rwe->properties.attachLists(&proplist, 1);
        }
        //将协议添加到rwe中
        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 {
                objc::unattachedCategories.attachToClass(cls, previously,
                                                         ATTACH_CLASS_AND_METACLASS);
            }
        }
        objc::unattachedCategories.attachToClass(cls, cls,
                                                 isMeta ? ATTACH_METACLASS : ATTACH_CLASS);
    ......
    
    rwe的逻辑

    方法列表添加至rwe逻辑如下

    • 获取ro的baseMethods
    • 通过prepareMethodLists方法排序
    • 对rwe进行处理即通过attachLists插入
    方法排序prepareMethodLists方法
    static void 
    prepareMethodLists(Class cls, method_list_t **addedLists, int addedCount,
                       bool baseMethods, bool methodsFromBundle, const char *why)
    {
        runtimeLock.assertLocked();
        // 将方法列表添加到数组中。
        // 重新分配未固定的方法列表。
        // 新方法预先添加到方法列表数组中。
        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*/);
            }
        }
    

    prepareMethodLists的源码实现,内部是通过fixupMethodList方法排序

    fixupMethodList修复方法列表,即重新排序
    static void 
    fixupMethodList(method_list_t *mlist, bool bundleCopy, bool sort)
    {
        runtimeLock.assertLocked();
        ASSERT(!mlist->isFixedUp());
    
        if (!mlist->isUniqued()) {
            mutex_locker_t lock(selLock);
        
            for (auto& meth : *mlist) {
                const char *name = sel_cname(meth.name());
                meth.setName(sel_registerNameNoLock(name, bundleCopy));
            }
        }
    
        /// 按选择器地址排序。
        // 不要尝试对小列表进行排序,因为它们是不可变的。
        // 不要尝试对非标准大小的大列表进行排序,如 stable_sort
        // 不会正确复制条目。
        if (sort && !mlist->isSmallList() && mlist->entsize() == method_t::bigSize) {
            method_t::SortBySELAddress sorter;
            std::stable_sort(&mlist->begin()->big(), &mlist->end()->big(), sorter);
        }
        
        // 将方法列表标记为唯一且已排序。
        // 不能标记小列表,因为它们是不可变的。
        if (!mlist->isSmallList()) {
            mlist->setFixedUp();
        }
    }
    

    fixupMethodList中加入自定义打印,验证方法排序

    // 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());
                
                 printf("上面 : %s - %p\n",name,meth.name());
                
                meth.setName(sel_registerNameNoLock(name, bundleCopy));
            }
        }
    
        // 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.
        if (sort && !mlist->isSmallList() && mlist->entsize() == method_t::bigSize) {
            method_t::SortBySELAddress sorter;
            std::stable_sort(&mlist->begin()->big(), &mlist->end()->big(), sorter);
        }
        
        printf("****************");
        for (auto& meth : *mlist) {
            const char *name = sel_cname(meth.name());
            printf("下面 : %s - %p\n",name,meth.name());
        }
    
    排序前 排序后

    排序后的地址是由小到大排列的,验证了fixupMethodList函数的排序功能。

    关于这部分的流程
    _read_images->read_class(地址和名称)->relizeClassWithoutSwift(对于ro、rw的操作,对superclass、isa也做了处理)->methodizeClass->prepareMethodLists->fixupMethodList (方法排序)

    懒加载类与非懒加载类

    开篇我们在寻找class的操作入口时,给LGPerson添加了+ (void)load方法会执行到非懒加载类源码方法中。所以懒加载类非懒加载类的区别就是是否实现了+load方法

    • 非懒加载类即程序启动时就会进行以上ro、rw、排序等耗时操作,效率很低。
    • 苹果为了提高效率采用了按需加载,也就是懒加载类,等需要时再加载。

    为什么实现load方法就会变成非懒加载类?
    原因是load方法会提前加载(如果类存在,load方法会在load_images中调用)

    懒加载类在什么时候加载? 答案:在调用方法的时候加载

    调试验证懒加载类的加载时机
    • 注释掉LGPerson中的+load方法,并在main中实例化person
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            // class_data_bits_t
            LGPerson * person = [LGPerson alloc];
        }
        return 0;
    }
    
    • realizeClassWithoutSwift方法中添加该类的断点
    image.png
    • 打印堆栈信息查看懒加载类调用时机
    (lldb) bt
    * thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
      * frame #0: 0x00000001002ff859 libobjc.A.dylib`realizeClassWithoutSwift(cls=0x0000000100008298, previously=0x0000000000000000) at objc-runtime-new.mm:2654:9 [opt]
        frame #1: 0x000000010030fe08 libobjc.A.dylib`realizeClassMaybeSwiftMaybeRelock(cls=0x0000000100008298, lock=<unavailable>, leaveLocked=true) at objc-runtime-new.mm:2920:9 [opt]
        frame #2: 0x0000000100307103 libobjc.A.dylib`lookUpImpOrForward [inlined] realizeClassMaybeSwiftAndLeaveLocked(cls=0x0000000100008298, lock=<unavailable>) at objc-runtime-new.mm:2943:12 [opt]
        frame #3: 0x00000001003070f6 libobjc.A.dylib`lookUpImpOrForward [inlined] realizeAndInitializeIfNeeded_locked(inst=0x00000001000082c0, cls=0x0000000100008298) at objc-runtime-new.mm:6384 [opt]
        frame #4: 0x00000001003070f6 libobjc.A.dylib`lookUpImpOrForward(inst=0x00000001000082c0, sel="alloc", cls=0x0000000100008298, behavior=<unavailable>) at objc-runtime-new.mm:6499 [opt]
        frame #5: 0x00000001002ededb libobjc.A.dylib`_objc_msgSend_uncached at objc-msg-x86_64.s:1153
        frame #6: 0x0000000100003bea KCObjcBuild`main(argc=<unavailable>, argv=<unavailable>) at main.m:42:23 [opt]
        frame #7: 0x00007fff20369621 libdyld.dylib`start + 1
    

    得出结论
    此时bt下查看函数调用栈,发现了lookUpImpOrForward函数,所以懒加载类的加载是在消息的慢速查找流程中调用

    懒加载类非懒加载类加载时机如下图

    加载时机

    分类的本质探索

    下面讨论分类是如何加载到类中的,以及分类和类搭配使用的情况。在main中定义LGPerson的分类LG

    // 这里给分类添加了协议<NSObject>
    @interface LGPerson (LG) <NSObject>
    @property (nonatomic, copy) NSString *cate_name;
    @property (nonatomic, assign) int cate_age;
    
    - (void)cate_instanceMethod1;
    - (void)cate_instanceMethod2;
    + (void)cate_classMethod3;
    
    @end
    
    @implementation LGPerson (LG)
    - (void)cate_instanceMethod1{
        NSLog(@"%s",__func__);
    }
    - (void)cate_instanceMethod2{
        NSLog(@"%s",__func__);
    }
    + (void)cate_classMethod3{
        NSLog(@"%s",__func__);
    }
    @end
    

    探索分类的本质,有以下三种方式

    • 通过clang -rewrite-objc main.m -o main.cpp
    • 通过Xcode文档搜索Category
    • 通过objc源码搜索category_t

    方式一: 使用clang -rewrite-objc main.m -o main.cpp将main.m文件转成 main.cpp,查看底层编译

    struct _category_t {
        const char *name; //名字为LG
        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;//属性
    };
    
    static struct _category_t _OBJC_$_CATEGORY_LGPerson_$_LG __attribute__ ((used, section ("__DATA,__objc_const"))) = 
    {
        "LGPerson",
        0, // &OBJC_CLASS_$_LGPerson,
        (const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_LGPerson_$_LG,
        (const struct _method_list_t *)&_OBJC_$_CATEGORY_CLASS_METHODS_LGPerson_$_LG,
        // 添加的<NSObject>协议
        (const struct _protocol_list_t *)&_OBJC_CATEGORY_PROTOCOLS_$_LGPerson_$_LG,
        (const struct _prop_list_t *)&_OBJC_$_PROP_LIST_LGPerson_$_LG,
    };
    
    static struct _category_t *L_OBJC_LABEL_CATEGORY_$ [1] __attribute__((used, section ("__DATA, __objc_catlist,regular,no_dead_strip")))= {
        &_OBJC_$_CATEGORY_LGPerson_$_LG,
    };
    
    // 添加的协议_OBJC_PROTOCOL_NSObject
    static struct /*_protocol_list_t*/ {
        long protocol_count;  // Note, this is 32/64 bit
        struct _protocol_t *super_protocols[1];
    } _OBJC_CATEGORY_PROTOCOLS_$_LGPerson_$_LG __attribute__ ((used, section ("__DATA,__objc_const"))) = {
        1,
        &_OBJC_PROTOCOL_NSObject
    };
    
    • 分类被翻译成c++文件后,可以看出_category_t是一个结构体
    • 分类没有元类的概念,所以instance_methods存储实例方法,class_methods存储类方法
    • 我们可以找到实例方法类方法的实现代码,但是分类中定义的属性没有相应的set、get方法(我们可以通过关联对象来设置,关于如何设置关联对象,我们将在后续的扩展中进行说明),所以分类不会自动生成方法
    • 在结构体中我们没有找到ivar的列表,所以分类中不能声明成员变量

    方式二: 通过Xcode文档搜索 Category

    image.png

    去底层objc源码中查阅objc_category如下

    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;
    }  
    

    方式三:通过objc源码搜索 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;
        }
    };
    

    分类加载的引入

    其中查看methodizeClass的源码实现,可以发现类的数据和分类的数据是分开处理的,主要是因为在编译阶段就已经确定好了方法的归属位置(即实例方法存储在类中,类方法存储在元类中),而分类是后面才加进来的

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

    其中分类需要通过attatchToClass添加到类,然后才能在外界进行使用,在此过程我们了解到分类的加载主要分为三步:

    • 分类数据加载时机:根据类和分类是否实现load方法来区分不同的时机
    • attachCategories准备分类数据
    • attachLists将分类数据添加到主类中

    我们在源码中了解到与分类加载相关的线路有两条

    • attatchToClass -> attachCategories
    • load_categories_nolock -> attachCategories
      其源码实现我们下节课再探索...

    相关文章

      网友评论

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

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