美文网首页
Objective-C--Ivar存储原理

Objective-C--Ivar存储原理

作者: 人生看淡不服就干 | 来源:发表于2017-07-25 17:29 被阅读290次

    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<ivar_t, ivar_list_t, 0> {
        bool containsIvar(Ivar ivar) const {
            return (ivar >= (Ivar)&*begin()  &&  ivar < (Ivar)&*end());
        }
    };
    
    /***********************************************************************
    * entsize_list_tt<Element, List, FlagMask>
    * 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 <typename Element, typename List, uint32_t FlagMask>
    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<<align. The minimum alignment of an instance 
     *       variable depends on the ivar's type and the machine architecture. 
     *       For variables of any pointer type, pass log2(sizeof(pointer_type)).
     */
    OBJC_EXPORT BOOL class_addIvar(Class cls, const char *name, size_t size, 
                                   uint8_t alignment, const char *types) 
    

    文档中要求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<<alignment)-1;
        offset = (offset + alignMask) & ~alignMask;
    
        ivar_t& ivar = newlist->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存储原理

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