015-类的加载原理(中)

·  阅读 306
015-类的加载原理(中)

通过这篇文章可以获得什么

探索概览

类的加载(中).jpeg

如何找到执行class的rw、ro、rwt操作的入口

  • 调试主线
    • 通过最直接的方式:设置断点+写入打印代码动态调试
    • 只关心我自己自定义的class(FFPerson)的操作
    • 边缘OB,看看测试代码执行情况
  • 疑问监测点(对_read_images内所有关于class的操作的地方)
    • Realize non-lazy classes
    • realize future classes
  • 找到目标,到底是在哪里完成class的rw、ro、rwe的操作

插入的测试代码:

const char *mangledName = cls->nonlazyMangledName();
const char *bblvPersonName = "FFPerson";
if (strcmp(mangledName, bblvPersonName) == 0) {
    printf("%s -- BBLv -- %s\n",__func__,mangledName);
}
复制代码

图解释义

  • 分别对3770行3785行3799行设置了调试断点
  • 绿色阴影内为主动添加的代码(非底层原始代码)
  • 红色框表示的是区域划分

疑问点检测.png

案例代码

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSLog(@"Hello, World!");
        FFPerson *objc = [FFPerson alloc];
    }
}
复制代码

调试结果:

2021-07-16 14:15:19.872310+0800 KCObjcBuild[2974:114580] Hello, World!
Program ended with exit code: 0
复制代码

没有任何我自定义的打印,未跟预想一样,走进断点。就跟什么一样,兴兴奋奋的第一次,但是找不到入口,烦的要命.......

疑问点一:只是添加了一句打印,为什么就断点就没有断住呢?同时为什么其他的断点就可以进呢?

调整案例代码

@implementation FFPerson

+ (void)load {
    NSLog(@"%s",__func__);
}
@end
复制代码

新的调试结果

Realize non-lazy classes -- _read_images -- BBLv -- FFPerson
2021-07-16 14:38:59.313671+0800 KCObjcBuild[3105:124123] +[FFPerson load]
2021-07-16 14:38:59.314300+0800 KCObjcBuild[3105:124123] Hello, World!
复制代码

结论:终于找到了入口,在Realize non-lazy classes,然后向下执行的是realizeClassWithoutSwift函数。终于可以愉快的向下玩耍了,向更深处出发。

疑问点二:如果我就是不添加load方法,那么这个类的实例方法与类方法在什么时候加载呢?

realizeClassWithoutSwift(类的实现)

源码:

/***********************************************************************
* 对类 cls 执行第一次初始化,
* 包括分配其读写数据。
* 不执行任何 Swift 端初始化。
* 返回类的真实类结构。
* 锁定:runtimeLock 必须由调用者写锁定
**********************************************************************/
static Class realizeClassWithoutSwift(Class cls, Class previously)
{
    runtimeLock.assertLocked();
    
    //插入的测试代码,为了指定当前类是FFPerson,对于自己创建的类与添加的方法,方便探索
    const char *mangledName = cls->nonlazyMangledName();
    const char *bblvPersonName = "FFPerson";
    if (strcmp(mangledName, bblvPersonName) == 0) {
        printf("Realize non-lazy classes -- %s -- BBLv -- %s\n",__func__,mangledName);
    }

    //创建rw、superclass、metacls
    class_rw_t *rw;
    Class supercls;
    Class metacls;

    if (!cls) return nil;
    if (cls->isRealized()) {
        validateAlreadyRealizedClass(cls);
        return cls;
    }
    ASSERT(cls == remapClass(cls));

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

    cls->cache.initializeToEmptyOrPreoptimizedInDisguise();

#if FAST_CACHE_META
    if (isMeta) cls->cache.setBit(FAST_CACHE_META);
#endif

    // 为这个类选择一个索引。
    // 如果索引没有更多可用索引,则设置 cls->instancesRequireRawIsa
    cls->chooseClassArrayIndex();

    if (PrintConnecting) {
        _objc_inform("CLASS: realizing class '%s'%s %p %p #%u %s%s",
                     cls->nameForLogging(), isMeta ? " (meta)" : "", 
                     (void*)cls, ro, cls->classArrayIndex(),
                     cls->isSwiftStable() ? "(swift)" : "",
                     cls->isSwiftLegacy() ? "(pre-stable swift)" : "");
    }

    // 实现父类和元类,如果还没有实现的情况下。
    // 这需要在上面设置 RW_REALIZED 之后完成,对于根类。
    // 这需要在选择类索引后完成,对于根元类。
    // 这假设这些类都没有 Swift 内容,或者 Swift 的初始化程序已经被调用。
    // 修正如果我们添加对 Swift 类的 ObjC 子类的支持,假设将是错误的。。
    supercls = realizeClassWithoutSwift(remapClass(cls->getSuperclass()), nil);
    metacls = realizeClassWithoutSwift(remapClass(cls->ISA()), nil);

#if SUPPORT_NONPOINTER_ISA
    if (isMeta) {
        // 元类不需要来自非指针 ISA 的任何特征
        // 这允许 objc_retain/objc_release 中的类的 faspath。
        cls->setInstancesRequireRawIsa();
    } else {
        // 为某些类和/或平台禁用非指针 isa。
        // 设置实例RequireRawIsa。
        bool instancesRequireRawIsa = cls->instancesRequireRawIsa();
        bool rawIsaIsInherited = false;
        static bool hackedDispatch = false;
        
        //这里可以通过设置环境变量对DisableNonpointerIsa进行控制
        if (DisableNonpointerIsa) {
            // 环境或应用 SDK 版本禁用非指针 isa
            instancesRequireRawIsa = true;
        }
        else if (!hackedDispatch  &&  0 == strcmp(ro->getName(), "OS_object"))
        {
            // 对 libdispatch 等人的 hack - isa 也充当 vtable 指针
            hackedDispatch = true;
            instancesRequireRawIsa = true;
        }
        else if (supercls  &&  supercls->getSuperclass()  &&
                 supercls->instancesRequireRawIsa())
        {
            // This is also propagated by addSubclass()
            // but nonpointer isa setup needs it earlier.
            // Special case: instancesRequireRawIsa does not propagate
            // from root class to root metaclass
            instancesRequireRawIsa = true;
            rawIsaIsInherited = true;
        }

        if (instancesRequireRawIsa) {
            cls->setInstancesRequireRawIsaRecursively(rawIsaIsInherited);
        }
    }
// SUPPORT_NONPOINTER_ISA
#endif

    // 在重新映射的情况下更新父类和元类
    cls->setSuperclass(supercls);
    cls->initClassIsa(metacls);

    // Reconcile instance variable offsets / layout.
    // This may reallocate class_ro_t, updating our ro variable.
    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
    if (ro->flags & RO_HAS_CXX_STRUCTORS) {
        cls->setHasCxxDtor();
        if (! (ro->flags & RO_HAS_CXX_DTOR_ONLY)) {
            cls->setHasCxxCtor();
        }
    }
    
    // Propagate the associated objects forbidden flag from ro or from
    // the superclass.
    if ((ro->flags & RO_FORBIDS_ASSOCIATED_OBJECTS) ||
        (supercls && supercls->forbidsAssociatedObjects()))
    {
        rw->flags |= RW_FORBIDS_ASSOCIATED_OBJECTS;
    }

    // 将此类连接到其fu类的子类列表
    if (supercls) {
        addSubclass(supercls, cls);
    } else {
        addRootClass(cls);
    }

    // Attach categories
    methodizeClass(cls, previously);

    return cls;
}
复制代码

关键点一:设置完整的class的rw数据

// 获取类中的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);
}
复制代码

关键点二:设置父类于元类的指向(isa的指向链)

supercls = realizeClassWithoutSwift(remapClass(cls->getSuperclass()), nil);
metacls = realizeClassWithoutSwift(remapClass(cls->ISA()), nil);

cls->setSuperclass(supercls);
cls->initClassIsa(metacls);
复制代码

realizeClassWithoutSwift流程图

流程图.png

将源码run起来,真实的体验一下这个过程

一阶段:递归流程

通过对realizeClassWithoutSwift源码的解读和流程图详细的描述,基本得到了类加载过程中核心部分,设置rw数据,但是这里面涉及到了isa的指向链关系,在设置当前类FFPerson的时候,同时设置了superClassmetaClass,同时metaClass的isa指向了RootMetaClass

执行步骤:

  1. 当前cls对象为FFPerson,通过_read_images读取到当前的FFPerson,然后调用realizeClassWithoutSwift设置FFPerson的rw数据

  2. 当前cls对象为FFPerson,通过realizeClassWithoutSwift方法内的supercls = realizeClassWithoutSwift(remapClass(cls->getSuperclass()), nil);即在此调用此方法设置FFPerson的父类NSObject,但是此时父类NSObject是已经实例话的类cls->isRealized判断为true,直接return cls,不会再次触发superclassmetaclass

  3. 当前cls对象为FFPerson,通过realizeClassWithoutSwift方法内的metacls = realizeClassWithoutSwift(remapClass(cls->ISA()), nil);即在此调用此方法设置FFPerson的元类

  4. 当前cls对象为FFPerson(metaclass),通过realizeClassWithoutSwift方法内的supercls = realizeClassWithoutSwift(remapClass(cls->getSuperclass()), nil);即在此调用此方法设置FFPerson的元类的父类,也就是设置NSObject的元类,即根元类,但是此时是已经实例话的类cls->isRealized判断为true,直接return cls,不会再次触发superclassmetaclass

  5. 当前cls对象为FFPerson(metaclass),metacls = realizeClassWithoutSwift(remapClass(cls->ISA()), nil);即在此调用此方法设置FFPerson的元类的根元类,但是此时是已经实例话的类cls->isRealized判断为true,直接return cls,不会再次触发superclassmetaclass,自此,isa指向关系完成。

简化此流程

FFPerson -> NSObject -> FFPerson(metaclass) -> NSObject(metaClass) -> RootMetaClass

测试代码的isa关系

FFperson -> metaclass -> rootMetaClass

测试代码的继承关系

FFPerson -> NSObject

测试代码关系链图解

第一次设置的是FFPerson的rw,即准备实例化FFPerson

realizeClass-FFPerson.png

第二次进入此方法准备实例化FFPerson的父类NSObject,但是NSObject此时已经实例化,这里直接回return,不会执行设置rw的逻辑

realizeClass-NSObject.png

第三次进入此方法准备实例FFPerson的元类(metaclass),此时元类并未初始化。

realizeClass-FFPerson(metaClass).png

第四次进入此方法实例话FFPerson元类的父类,即NSObject的元类,已经实例化,直接return

realizeClass-NSObject(metaClass).png

第五次进入此方法RootMetaClass

realizeClass-RootMetaClass.png

通过以上流程可以得出一阶段结论:此递归流程过程中FFPerson与FFPerson的元类未实例化,其他的均已完成实例化。

二阶段:找到当前实例化的FFPerson与FFPerson的元类的ro数据内的methodList

案例代码:

@interface FFPerson : NSObject
// 实例方法
- (void)likeFood;
- (void)likeLive;
- (void)likeSleep;
- (void)likeGirls;
//类方法
+ (void)enjoyLife;
@end
**@implementation FFPerson
//类方法
+ (void)load {
    NSLog(@"%s", __func__);
}
+ (void)enjoyLife {
    NSLog(@"%s", __func__);
}
//实例方法
- (void)likeFood {
    NSLog(@"%s", __func__);
}
- (void)likeLive {
    NSLog(@"%s", __func__);
}
- (void)likeSleep {
    NSLog(@"%s", __func__);
}
- (void)likeGirls{
    NSLog(@"%s", __func__);
}
@end
复制代码

FFPerson

FFPerson-ro.png

FFPerson(metaClass)

FFPerson(metaClass)-ro.png

方法排序

函数调用流程

fixupMethodList.jpeg

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

    method_list_t *list = ro->baseMethods();
    if (list) {
        //方法列表的准备:去排序
        prepareMethodLists(cls, &list, 1, YES, isBundleClass(cls), nullptr);
        if (rwe) rwe->methods.attachLists(&list, 1);
    }
复制代码

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*/);
        }
    }
复制代码

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中加入自定义打印,OB此函数是否真的做了排序

改造后的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());
            printf("排序前 -> %s - %p",name,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);
    }
    
    for (auto& meth : *mlist) {
        const char *name = sel_cname(meth.name());
        printf("排序后 -> %s - %p",name,meth.name());
    }
    
    // 将方法列表标记为唯一且已排序。
    // 不能标记小列表,因为它们是不可变的。
    if (!mlist->isSmallList()) {
        mlist->setFixedUp();
    }
}
复制代码

控制台输出结果

FFPerson

Realize non-lazy classes -- _read_images -- BBLv -- FFPerson
排序前 -> likeFood - 0x100003f6a
排序前 -> likeLive - 0x100003f73
排序前 -> likeSleep - 0x100003f7c
排序前 -> likeGirls - 0x100003f86
排序前 -> AA_V587 - 0x100003f90
排序前 -> say_1 - 0x100003f98
---------------------------------
排序后 -> likeFood - 0x100003f6a
排序后 -> likeLive - 0x100003f73
排序后 -> likeSleep - 0x100003f7c
排序后 -> likeGirls - 0x100003f86
排序后 -> AA_V587 - 0x100003f90
排序后 -> say_1 - 0x100003f98
(lldb) 
复制代码

FFPerson的metaClass

Realize non-lazy classes -- _read_images -- BBLv -- FFPerson
排序前 -> load - 0x100003f6a
排序前 -> enjoyLife - 0x100003f6f
---------------------------------
排序后 -> enjoyLife - 0x100003f6f
排序后 -> load - 0x7fff7b9b1ea0
(lldb) 
复制代码

结论:

  • FFPerson类并未真实观察到顺序有变化,因为排序之前的地址已经排好序了,为了验证,又多加了2个函数AA_V587与say_1,也没有发生变化

  • FFPerson的metaClass发生了排序,验证了fixupMethodList函数的排序功能

类的加载方式

懒加载与非懒加载.jpeg

解决探索过程中出现的疑问点

疑问点一:只是添加了一句打印,为什么就断点就没有断住呢?同时为什么其他的断点就可以进呢?

还不是因为是第一次,如此莽撞,不够细心。如果你足够细心,你可以发现一句很关键的注释// Realize non-lazy classes (for +load methods and static instances),关于关心位置一的部分,实则为初始化非懒加载的类,用于load方法和静态实例,这一下就明朗了,给FFPerson类添加load方法就OK了,完美结局。

@implementation FFPerson

+ (void)load {
    NSLog(@"%s",__func__);
}
@end
复制代码

疑问点二:如果我就是不添加load方法,那么这个类的实例方法与类方法在什么时候加载呢?

非懒加载类: 当前类即FFPerson是否实现了load方法决定了这个类是否为懒加载类。上述所有论证都为实现了load的情况下,即为非懒加载类。

懒加载类:如果是懒加载类,那么在dyld加载程序阶段不会有任何操作,所有的数据加载都来自于main函数之后了,当FFPerson发生第一次消息发送的时候,开始加载数据。通过bt打印了函数调用过程,验证了方法加载走的是慢速查找流程,即lookUpImpOrForward

FFPerson懒加载.png

分类:
iOS
标签: