440.能否向编译后得到的类中增加实例变量?能否向运行时创建的类中添加实例变量?为什么?(面试点: 不能向编译后得到的类中增加实例变量; 能向运行时创建的类中添加实例变量; 1.因为编译后的类已...

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

  • 因为编译后的类已经注册在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.能否向编译后得到的类中增加实例变量?能否向运行时创建的类中添加实例变量?为什么?(面试点: 不能向编译后得到的类中增加实例变量; 能向运行时创建的类中添加实例变量; 1.因为编译后的类已...)