OC底层原理(13)类的加载原理(中)

294 阅读10分钟

realizeClassWithoutSwift实现类

在上一篇 类的加载原理(上) 分析 read_images流程中,发现会对类进行一些修复工作,比如修复编译阶段selector的混乱问题、混乱类的处理等;同时会将类的名称与类进行关联,插入对照表中,并更新内存中的类表。

文章的最后找到了 非懒加载类的 加载相关代码:

Xnip2022-07-18_10-01-49.png

1. 源码分析

接着上一篇,分析 realizeClassWithoutSwift,上源码:

static Class realizeClassWithoutSwift(Class cls, Class previously)
{
    runtimeLock.assertLocked();
    class_rw_t *rw;
    Class supercls;
    Class metacls;
    
    if (!cls) return nil;
    // cls 已经实现,直接 return cls;
    if (cls->isRealized()) {
        validateAlreadyRealizedClass(cls);
        return cls;
    }
    ASSERT(cls == remapClass(cls));

    // data() - ro -> 开辟rw
    // 从mach-o文件中读取数据data,转成class_ro_t的数据结构
    // ro是在编辑阶段即确定下来的数据结构,而rw是运行时的结构,所以需要开辟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 = objc::zalloc<class_rw_t>();
        rw->set_ro(ro);
        rw->flags = RW_REALIZED|RW_REALIZING|isMeta;
        cls->setData(rw);
    }
    // 干净内存地址ro  脏内存地址rw
    // 运行时 - 内存  ro - copy - rw  内存操作比较严重
    // 并不是每一个类都需要插入,进行修改的很少,(rw扩展会导致内存占用很多,直接复制ro),如果动态性扩展一个rwe
        
    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
    cls->chooseClassArrayIndex();

    if (PrintConnecting) { ... }

    // 递归,加载父类、元类的实现
    supercls = realizeClassWithoutSwift(remapClass(cls->getSuperclass()), nil);
    metacls = realizeClassWithoutSwift(remapClass(cls->ISA()), nil);

#if SUPPORT_NONPOINTER_ISA
    if (isMeta) {
        // Metaclasses do not need any features from non pointer ISA
        // This allows for a faspath for classes in objc_retain/objc_release.
        cls->setInstancesRequireRawIsa();
    } else {
        // Disable non-pointer isa for some classes and/or platforms.
        // Set instancesRequireRawIsa.
        bool instancesRequireRawIsa = cls->instancesRequireRawIsa();
        bool rawIsaIsInherited = false;
        static bool hackedDispatch = false;

        if (DisableNonpointerIsa) {
            // Non-pointer isa disabled by environment or app SDK version
            instancesRequireRawIsa = true;
        }
        else if (!hackedDispatch  &&  0 == strcmp(ro->getName(), "OS_object"))
        {
            // hack for libdispatch et al - isa also acts as vtable pointer
            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

    // Update superclass and metaclass in case of remapping
    // 设置 父类、isa
    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);
    
    // c++函数
    // 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;
    }

    // 建立类 子类的双向链表关系
    // Connect this class to its superclass's subclass lists
    if (supercls) {
        addSubclass(supercls, cls);
    } else {
        addRootClass(cls);
    }

    // Attach categories 附加类别 - 方法化当前的类,对类进行扩展
    // Attach categories
    methodizeClass(cls, previously);

    return cls;
}

1.1 判空

首先进行相关变量的声明,判断类是否已经实现,如果已经实现直接返回。用于递归控制,因为后面的流程中会进行递归实现父类和元类,根类的父类是nil元类的isa指向自己,所以这样可以保证类只会被初始化一次。

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

2.2 rw 初始化

rw 初始化。这里涉及到干净内存clean memory脏内存dirty memory的概念,在 类的底层探究(中)中有说明。

  • clean memory:指加载后不会发生更改的内存。
  • dirty memory:指在进程运行时会发生更改的内存。

ro(readonly) 属于 clean memory在编辑时即确定的内存空间,只读,加载后不会发生改变的内存空间,包括类名称、方法、协议和实例变量的信息;

rw的数据空间属于dirty memoryrw是运行时的结构,可读可写,由于其动态性,可以往类中添加属性、方法、协议。在运行时会发生变更的内存

rwe类的额外信息。在WWDC2020中也提到,只有不到10%的类真正的更改了他们的方法,并不是每一个类都需要插入数据,进行修改的类很少,避免资源的消耗,所以就有了rwe

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 = objc::zalloc<class_rw_t>();
    rw->set_ro(ro);
    rw->flags = RW_REALIZED|RW_REALIZING|isMeta;
    cls->setData(rw);
}

在此流程中,从 MachO 中获取的数据地址,根据 class_ro_t 格式进行强制,同时初始化 rw 的空间,并复制一份 ro 的数据放入rw中。

1.3. 递归处理,父类、元类的实现

    // 递归,加载父类、元类的实现
    supercls = realizeClassWithoutSwift(remapClass(cls->superclass), nil);
    metacls = realizeClassWithoutSwift(remapClass(cls->ISA()), nil);

1.4 Isa处理

在前面学习isa的时候,对于NONPOINTER_ISA进行了位域处理,指针优化,Isa的末尾位是1Isa不单单代表一个指针。而对于元类以及特殊情况下的场景的一些类,无需开启指针优化的类,使用Raw IsaIsa的末尾位是0

#if SUPPORT_NONPOINTER_ISA
    if (isMeta) {
        // Metaclasses do not need any features from non pointer ISA
        // This allows for a faspath for classes in objc_retain/objc_release.
        // 元类不需要非指针ISA的任何特性
        cls->setInstancesRequireRawIsa();
    } else {
        // Disable non-pointer isa for some classes and/or platforms.
        // Set instancesRequireRawIsa.
        bool instancesRequireRawIsa = cls->instancesRequireRawIsa();
        bool rawIsaIsInherited = false;
        static bool hackedDispatch = false;

        //  是否有设置环境变量
        if (DisableNonpointerIsa) {
            // Non-pointer isa disabled by environment or app SDK version
            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;
        }
        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

1.5 建立子父类关系

建立子类与父类的双定链表关系,保证子类能找到父类,父类也可以找到子类。初始化类的实例对象的大小是否有c++析构函数设置,以及关联对象的相关设置

// Update superclass and metaclass in case of remapping
// 设置 父类、isa
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);

// c++函数
// 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;
}

// 建立类 子类的双向链表关系
// Connect this class to its superclass's subclass lists
if (supercls) {
    addSubclass(supercls, cls);
} else {
    addRootClass(cls);
}

cls->setInstanceSize(ro->instanceSize); 这个有木有很熟悉,我们在创建对象的时候,class_createInstanceFromZone流程中,在进行对象大小初始化时,使用到了instanceSize

1.6 methodizeClass

方法化当前的类,向类中添加方法,协议、属性,同时对方法列表进行排序等操作。

// Attach categories 附加类别 - 方法化当前的类,对类进行扩展
// Attach categories
methodizeClass(cls, previously);

2. cls 数据变化

同样的方法,我们在流程中添加一份关键代码,过滤出我们想要研究的类YJPerson

const char * className = "YJPerson";
if (strcmp(class_getName(cls), className) == 0)
{
    printf("hello YJPerson...");
}

设置断点,分别判断在rw初始化前/后 lldb打印cls的数据结构的变化。

2.1 rw 初始化前

运行程序,打印cls的内容:

Xnip2022-07-18_11-36-01.png

rw 在初始化之前,类只是有对应的成员结构,内部都还没有赋值,并且 ro无法读取

2.2 rw 初始化后

运行程序,打印cls的内容:

Xnip2022-07-18_12-00-04.png

  • auto ro = (const class_ro_t *)cls->data();获取MachO文件中的数据地址,按照class_ro_t格式进行强转。
  • auto isMeta = ro->flags & RO_META;判断是否为元类。
  • rw = objc::zalloc<class_rw_t>();初始化rw,将ro复制给rw,并将clsbits.data设置为rw

3. methodizeClass分析

realizeClassWithoutSwift完成类的初始化后,rw也已经创建,并能够从cls中获取相关的属性、方法等。那么realizeClassWithoutSwift中最后一行代码methodizeClass方法做了什么呢 ?

3.1 源码分析

首先解读一下源码:

/***********************************************************************
* methodizeClass
* 修复了cls的方法列表,协议列表和属性列表。
* 附加未完成的类别。
* 锁定:runtimeLock必须被调用者持有
**********************************************************************/
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();

    // Methodizing for the first time
    if (PrintConnecting) {
        _objc_inform("CLASS: methodizing class '%s' %s", 
                     cls->nameForLogging(), isMeta ? "(meta)" : "");
    }

    // Install methods and properties that the class implements itself.
    // 安装类自己实现的方法和属性。
    method_list_t *list = ro->baseMethods();
    if (list) {
        prepareMethodLists(cls, &list, 1, YES, isBundleClass(cls), nullptr);
        if (rwe) rwe->methods.attachLists(&list, 1);
    }

    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.
    // 根类获得额外的方法实现
    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);

    ...
}

对方法、属性、协议进行处理。见下图:

Xnip2022-07-18_13-18-28.png

rwe 为空,很显然,此时还没有对类进行相关的扩展操作,所以 rwe 还没有被创建初始化。此时针对方法、属性、协议的添加操作时无效的

继续向下看,找到了类初始化过程中非常关键的步骤,向类中添加分类方法、协议等 Xnip2022-07-18_17-13-12.png

3.2 prepareMethodLists

前面在对 rwe 方法进行处理之前,调用了 prepareMethodLists:

Xnip2022-07-18_13-23-58.png

那么 prepareMethodLists方法做了哪些操作呢?上源码:

Xnip2022-07-18_13-44-42.png

核心流程,fixupMethodList,根据注释:根据需要对selector进行修复。进入fixupMethodList方法,查看实现流程。见下图:

Xnip2022-07-18_15-41-30.png

4. attachCategories 探索

methodizeClass流程中,还有一行关键代码没有解析!将分类相关内容添加到类中。见下面代码:

objc::unattachedCategories.attachToClass(cls, cls, isMeta ? ATTACH_METACLASS : ATTACH_CLASS);

继续运行代码,进入attachToClass方法中,见下图:

Xnip2022-07-18_17-34-55.png

哎呀我去,跟丢了。。。

找到 attachCategories 方法:

// 将方法列表、属性和协议从类别附加到一个类。
// 假设猫的类别都是加载的,并按加载顺序排序,
// 最古老的类别先。
static void
attachCategories(Class cls, const locstamped_category_t *cats_list, uint32_t cats_count, int flags)
{
    ...
}

注释说这个方法就是将类别中的方法、属性和协议添加到类中,那么,这个方法肯定调用了,只是没在按上面调试流程调用,全局搜索 attachCategories:

Xnip2022-07-18_17-46-40.png

搜索发现 attachCategoriesload_categories_nolock 中调用了,继续搜索 load_categories_nolock:

Xnip2022-07-18_17-53-20.png

搜索发现 load_categories_nolockloadAllCategories 中调用了,继续搜索 loadAllCategories:

Xnip2022-07-18_17-55-44.png

  • 梳理一下流程:
    • load_images
    • loadAllCategories
    • load_categories_nolock
    • attachCategories

4.1 分类处理初探

创建一个YJPerson的分类,并在分类中添加一个方法sayCate。见下面代码:

@implementation YJPerson (YJ)
- (void)sayCate {
    NSLog(@"调用了 : %s", __func__ );
}
@end

attachCategories是向类中添加分类的内容,在load_categories_nolock方法中添加过滤条件,过滤出我们所关心的YJPerson类的分类处理流程!见下图:

Xnip2022-07-18_18-09-17.png

运行项目发现没有断住,为什么呢?该方法是由load_images调用的,那么与load方法 是否有关系呢?

修改分类LGPerson(YJ),在其中添加load方法,如下:

@implementation YJPerson (YJ)
+ (void)load {}

- (void)sayCate {
    NSLog(@"调用了 : %s", __func__ );
}
@end

重新运行,nice 断住了

Xnip2022-07-18_18-19-21.png

lldb 调试,输出 cat 信息,正是 我们自定义的 YJPerson(YJ),且有 sayCate 实例方法

到这里貌似一切谜底都解开了,但是别忘了类和分类中都实现了load方法,也就是 非懒加载的类和分类 处理流程谜底算是解开了。

5. 懒加载类的初始化流程分析

  • 非懒加载类的加载流程:
    • map_images
    • map_images_nolock
    • _read_images
    • realizeClassWithoutSwift
    • methodizeClass

首先把 YJPerson 类和类别 中的 load 方法都注释掉,这时候直接运行项目发现没有走 methodizeClass 方法。啥也没做就跑到 main 里面了

Xnip2022-07-18_18-50-31.png

把初始化 YJPerson 的代码松开:

Xnip2022-07-18_18-52-59.png

哎呦进来了,由此发现在懒加载的类在进行第一次消息发送时,会对类进行加载

  • 懒加载类的加载流程为:
    • lookUpImpOrForward
    • realizeClassMaybeSwiftMaybeRelock
    • realizeClassWithoutSwift
    • methodizeClass