美文网首页
440.能否向编译后得到的类中增加实例变量?能否向运行时创建的类

440.能否向编译后得到的类中增加实例变量?能否向运行时创建的类

作者: 枫叶1234 | 来源:发表于2021-01-30 16:11 被阅读0次

    不能向编译后得到的类中增加实例变量!
    能向运行时创建的类中添加实例变量!

    • 因为编译后的类已经注册在runtime中,类结构体中的objc_ivar_list 实例变量的链表和instance_size实例变量的内存大小已经确定,同时runtime 会调用class_setIvarLayout 或 class_setWeakIvarLayout来处理strong weak引用,所以不能向存在的类中添加实例变量。

    • 运行时创建的类是可以添加实例变量,调用 class_addIvar 函数,但是得在调用 objc_allocateClassPair 之后,objc_registerClassPair 之前,原因同上。

    在苹果文档中,对 class_addIvar 函数有下面一段话:

    This function may only be called after objc_allocateClassPair(_:_:_:) and before objc_registerClassPair(_:). Adding an instance variable to an existing class is not supported.
    The class must not be a metaclass. Adding an instance variable to a metaclass is not supported.
    这个功能只能在 objc_allocateClassPair(_:_:_:) 之后和 objc_registerClassPair(_:) 之前调用。不支持将实例变量添加到现有的类。
    该类不能是元类。不支持将实例变量添加到元类。
    

    文档是说不能将此函数用于已有的类,必须是动态创建的类,为了能够知道为何会这样,我们需要翻阅一下苹果开源的 runtime 源码。

    1.首先看一下关于 objc_allocateClassPair 函数的代码实现:

    去除干扰代码,我们寻找到下面的函数调用链条:
    objc_allocateClassPair -> objc_initializeClassPair_internal
    // 下面的代码已经被我大部分剔除,只留下我们分析所需要用到的代码
    static void objc_initializeClassPair_internal(Class superclass, const char *name, Class cls, Class meta)
    {
        // Set basic info
        cls->data()->flags = RW_CONSTRUCTING | RW_COPIED_RO | RW_REALIZED | RW_REALIZING;
        meta->data()->flags = RW_CONSTRUCTING | RW_COPIED_RO | RW_REALIZED | RW_REALIZING;
        cls->data()->version = 0;
        meta->data()->version = 7;
        
        // RW_CONSTRUCTING 类已分配但还未注册
        // RW_COPIED_RO class_rw_t->ro 来自 class_ro_t 结构的复制
        // RW_REALIZED //  class_t->data 的结构为 class_rw_t
        // RW_REALIZING // 类已开始分配,但并未完成
        // 以上几个宏都是对新类的class_rw_t结构设置基本信息
    }
    

    2.下面是class_addIvar的与我分析所需要的实现代码

    / 无关代码已经剔除
    BOOL 
    class_addIvar(Class cls, const char *name, size_t size, 
                  uint8_t alignment, const char *type)
    {
        if (!cls) return NO;
        if (!type) type = "";
        if (name  &&  0 == strcmp(name, "")) name = nil;
        rwlock_writer_t lock(runtimeLock);
        assert(cls->isRealized());
        // No class variables
        if (cls->isMetaClass()) {
            return NO;
        }
        // Can only add ivars to in-construction classes.
        if (!(cls->data()->flags & RW_CONSTRUCTING)) {
            return NO;
        }
    }
    // 重点在这最后一句,前面我们已经看到 objc_allocateClassPair 函数所分配的新类的flags位信息,在此处 & RW_CONSTRUCTING,必定为真,取反后跳过大括号向下执行。
    

    3.已经存在的类,经过测试,flag位为 RW_REALIZED|RW_REALIZING,设置函数如下:

    static Class realizeClass(Class cls)
    {
        runtimeLock.assertWriting();
        const class_ro_t *ro;
        class_rw_t *rw;
        Class supercls;
        Class metacls;
        bool isMeta;
        
        if (!cls) return nil;
        if (cls->isRealized()) return cls;
        assert(cls == remapClass(cls));
        // fixme verify class is not in an un-dlopened part of the shared cache?
        ro = (const class_ro_t *)cls->data();
        if (ro->flags & RO_FUTURE) {
            // This was a future class. rw data is already allocated.
            rw = cls->data();
            ro = cls->data()->ro;
            cls->changeInfo(RW_REALIZED|RW_REALIZING, RW_FUTURE);
        } else {
            // Normal class. Allocate writeable class data.
            rw = (class_rw_t *)calloc(sizeof(class_rw_t), 1);
            rw->ro = ro;
            rw->flags = RW_REALIZED|RW_REALIZING;
            cls->setData(rw);
        }
    }
    所以在经过条件 !((RW_REALIZED | RW_REALIZING) & RW_CONSTRUCTING) 时返回NO。
    

    以上便是对已有类不能使用 class_addIvar 函数的分析

    相关文章

      网友评论

          本文标题:440.能否向编译后得到的类中增加实例变量?能否向运行时创建的类

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