类的加载原理(中)

2021-08-13  本文已影响0人  浅墨入画

realizeClassWithoutSwift引入

如何找到执行class的rw、ro、rwe操作入口
<!-- 实现非懒加载的类 -->
// 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数据加载到内存中。主要步骤如下

类的初始化
    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

递归调用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

通过methodizeClass方法化类
// Attach categories 附加类别 -- 疑问:ro中也有方法列表 rw中也有方法列表,下面这个方法可以说明
    // 将ro数据写入到rw
    methodizeClass(cls, previously);

    return cls;

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

methodizeClass分析

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();
    ......

    // 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逻辑如下

方法排序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方法

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

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

调试验证懒加载类的加载时机
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // class_data_bits_t
        LGPerson * person = [LGPerson alloc];
    }
    return 0;
}
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将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
};

方式二: 通过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添加到类,然后才能在外界进行使用,在此过程我们了解到分类的加载主要分为三步:

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

上一篇下一篇

猜你喜欢

热点阅读