Objective-C--Ivar存储原理

Ivar是什么

Ivar(instance variable),实例变量,是对象中真正存储信息的变量。

一个对象的Ivar列表是怎么存储的?

结论就是Ivar列表是顺序存储的,最底层就是一个结构体数组,可以一步步查看源代码:

单个Ivar结构如下

typedef struct ivar_t *Ivar;
struct ivar_t {
    int32_t *offset;
    const char *name;
    const char *type;
    // alignment is sometimes -1; use alignment() instead
    uint32_t alignment_raw;
    uint32_t size;
...
};

获取一个Ivar方法如下,从中可以看出Ivar列表是顺序存储的

static ivar_t *getIvar(Class cls, const char *name)
{
    runtimeLock.assertLocked();

    const ivar_list_t *ivars;
    assert(cls->isRealized());
    if ((ivars = cls->data()->ro->ivars)) {
        for (auto& ivar : *ivars) {
            if (!ivar.offset) continue;  // anonymous bitfield

            // ivar.name may be nil for anonymous bitfields etc.
            if (ivar.name  &&  0 == strcmp(name, ivar.name)) {
                return &ivar;
            }
        }
    }

    return nil;
}

那么Ivar在类中是怎么存储的呢?

struct objc_class : objc_object {
    // Class ISA;
    Class superclass;
    cache_t cache;             // formerly cache pointer and vtable
    class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags

    class_rw_t *data() { 
        return bits.data();
    }
...
}

struct class_data_bits_t {
    // Values are the FAST_ flags above.
    uintptr_t bits;

    class_rw_t* data() {
        return (class_rw_t *)(bits & FAST_DATA_MASK);
    }
...
}

可见类中除了“ISA、superclass、cache”的数据全都存储在bits中;bits的data()返回的是class_rw_t结构,表示一个类需要读写数据;而我们寻找的Ivar存储在其中的只读数据部分,即const class_ro_t *ro;

struct class_rw_t {
    // Be warned that Symbolication knows the layout of this structure.
    uint32_t flags;
    uint32_t version;

    const class_ro_t *ro;

    method_array_t methods;
    property_array_t properties;
    protocol_array_t protocols;

    Class firstSubclass;
    Class nextSiblingClass;
...
}
struct class_ro_t {
    uint32_t flags;
    uint32_t instanceStart;
    uint32_t instanceSize;
#ifdef __LP64__
    uint32_t reserved;
#endif

    const uint8_t * ivarLayout;
    
    const char * name;
    method_list_t * baseMethodList;
    protocol_list_t * baseProtocols;
    const ivar_list_t * ivars;

    const uint8_t * weakIvarLayout;
    property_list_t *baseProperties;
...
};

终于见到我们想要的ivar_list了,那么他到底是个什么样的数据结构呢?

struct ivar_list_t : entsize_list_tt {
    bool containsIvar(Ivar ivar) const {
        return (ivar >= (Ivar)&*begin()  &&  ivar < (Ivar)&*end());
    }
};
/***********************************************************************
* entsize_list_tt
* Generic implementation of an array of non-fragile structs.
*
* Element is the struct type (e.g. method_t)
* List is the specialization of entsize_list_tt (e.g. method_list_t)
* FlagMask is used to stash extra bits in the entsize field
*   (e.g. method list fixup markers)
**********************************************************************/
template 
struct entsize_list_tt {
    uint32_t entsizeAndFlags;
    uint32_t count;
    Element first;

    Element& getOrEnd(uint32_t i) const { 
        assert(i <= count);
        return *(Element *)((uint8_t *)&first + i*entsize()); 
    }
 ...
}

通过注释我们也能看明白entsize_list_tt是个顺序存储的结构,因此Ivar是顺序存储的,为了加深理解,我们再看一下拷贝Ivar列表时如何分配Ivar的存储空间:

Ivar *class_copyIvarList(Class cls, unsigned int *outCount)
{
    const ivar_list_t *ivars;
    Ivar *result = nil;
    unsigned int count = 0;

    if (!cls) {
        if (outCount) *outCount = 0;
        return nil;
    }

    rwlock_reader_t lock(runtimeLock);

    assert(cls->isRealized());
    
    if ((ivars = cls->data()->ro->ivars)  &&  ivars->count) {
        result = (Ivar *)malloc((ivars->count+1) * sizeof(Ivar));
        
        for (auto& ivar : *ivars) {
            if (!ivar.offset) continue;  // anonymous bitfield
            result[count++] = &ivar;
        }
        result[count] = nil;
    }
    
    if (outCount) *outCount = count;
    return result;
}

为什么已注册的类不能添加Ivar,但却可以添加method?

先看一下runtime.h中关于添加Ivar的接口声明

/** 
 * Adds a new instance variable to a class.
 * 
 * @return YES if the instance variable was added successfully, otherwise NO 
 *         (for example, the class already contains an instance variable with that name).
 *
 * @note This function may only be called after objc_allocateClassPair and before objc_registerClassPair. 
 *       Adding an instance variable to an existing class is not supported.
 * @note The class must not be a metaclass. Adding an instance variable to a metaclass is not supported.
 * @note The instance variable's minimum alignment in bytes is 1<

文档中要求class_addIvar必须在 objc_allocateClassPair 之后且objc_registerClassPair之前调用,向一个已经注册的类添加Ivar是不支持的。

经过编译过程的类,在加载的时候已经注册了,根本没有时机让你添加实例变量;而运行时创建的新类,可以在objc_registerClassPair之前通过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; //已经注册的类,在这里就直接返回了
    }

    // Check for existing ivar with this name, unless it's anonymous.
    // Check for too-big ivar.
    // fixme check for superclass ivar too?
    if ((name  &&  getIvar(cls, name))  ||  size > UINT32_MAX) {
        return NO;
    }

    class_ro_t *ro_w = make_ro_writeable(cls->data());

    // fixme allocate less memory here
    
    ivar_list_t *oldlist, *newlist;
    if ((oldlist = (ivar_list_t *)cls->data()->ro->ivars)) {
        size_t oldsize = oldlist->byteSize();
        newlist = (ivar_list_t *)calloc(oldsize + oldlist->entsize(), 1);
        memcpy(newlist, oldlist, oldsize);
        free(oldlist);
    } else {
        newlist = (ivar_list_t *)calloc(sizeof(ivar_list_t), 1);
        newlist->entsizeAndFlags = (uint32_t)sizeof(ivar_t);
    }

    uint32_t offset = cls->unalignedInstanceSize();
    uint32_t alignMask = (1<get(newlist->count++);
#if __x86_64__
    // Deliberately over-allocate the ivar offset variable. 
    // Use calloc() to clear all 64 bits. See the note in struct ivar_t.
    ivar.offset = (int32_t *)(int64_t *)calloc(sizeof(int64_t), 1);
#else
    ivar.offset = (int32_t *)malloc(sizeof(int32_t));
#endif
    *ivar.offset = offset;
    ivar.name = name ? strdupIfMutable(name) : nil;
    ivar.type = strdupIfMutable(type);
    ivar.alignment_raw = alignment;
    ivar.size = (uint32_t)size;

    ro_w->ivars = newlist;
    cls->setInstanceSize((uint32_t)(offset + size));

    // Ivar layout updated in registerClass.

    return YES;
}

注册完类后为啥不让添加实例变量呢?

网上答案说已注册类的实例对象大小和内存布局都已经确定,如果添加实例变量就会破坏类的内部布局,这对编译型语言来说是灾难性的。其实还没有从逻辑上讲明白为啥不让添加实例变量。

我们知道一个类只有注册过才能用来创建对象,假设一个已经注册过的类创建了对象A,然后我们又给这个类增加了一个实例变量,并用这个类又创建了对象B,那么A和B的存储结构都不一样,那么A和B还能算是同一类对象吗?所以从逻辑上讲,也不能允许添加实例变量。

那为又啥能让添加方法呢?

因为方法列表存放在类对象中,为类对象增加一个方法,所有该类的实例对象都会拥有这个方法,因此实例对象之间没有造成差异,还是同一个类型。

你可能感兴趣的:(Objective-C--Ivar存储原理)