Runtime源代码解读3(成员变量列表)

2019-10-13html

类的成员变量列表描述类的对象须要保存哪些数据,包括这些数据的名称、类型、在对象内存空间中占用的区块、对齐方式准则信息。经过成员变量的内存布局信息,能够直接由对象内存地址定位成员变量的内存地址,实现高效的对象数据读写。bash

1、成员变量数据结构

成员变量的数据结构是ivar_t结构体,定义指向ivar_t结构体的指针ivar_t *类型为Ivarivar_t结构体的offsetsizealignment_raw共同界定了成员变量在对象内存空间中占用的区块,ivar_t包含如下成员:数据结构

  • offset:成员变量在对象内存空间中的偏移量;
  • name:成员变量的名称;
  • type:成员变量的类型编码,用字符串表示成员变量的类型,可参考苹果官方文档 Type Encodings
  • alignment_raw:成员变量的对齐准则,表示成员变量的对齐字节数为2^alignment_raw。例如,占用4字节的int类型成员变量alignment_raw为2,占用8字节的指针类型成员变量alignment_raw为3;
  • size:成员变量在对象内存空间中占用的空间大小;
typedef struct ivar_t *Ivar;

struct ivar_t {
    int32_t *offset;
    const char *name;
    const char *type;
    
    // alignment_raw有时值为-1,需调用alignment()获取成员变量的对齐准则
    uint32_t alignment_raw;
    uint32_t size;

    uint32_t alignment() const {
        if (alignment_raw == ~(uint32_t)0) return 1U << WORD_SHIFT;
        return 1 << alignment_raw;
    }
};
复制代码

注意:默认状况下,类的成员变量对齐方式和C语言结构体的原则是一致的。例如:继承自NSObjectSomeClass类依次包含boolintcharid类型的四个成员,SomeClass实例的内存图以下。注意到,bool类型的bo成员本该能够 1 bit 就能够表示但却占了4个字节的内存空间,这都是由于内存对齐原则。app

实例内存图.jpg

类的全部成员变量保存在一个顺序表容器中,其类型为ivar_list_t结构体,代码以下。ivar_list_t继承了entsize_list_tt顺序表模板,增长了bool containsIvar(Ivar ivar) const函数,用于查找传入的Ivar类型的ivar是否包含在成员变量列表中。框架

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());
    }
};
复制代码

2、entsize_list_tt 模板

entsize_list_tt是 runtime 定义的一种顺序表模板。entsize_list_tt<typename Element, typename List, uint32_t FlagMask>模板中,Element表示元素的类型,List表示所定义的顺序表容器类型名称,FlagMask用于从entsize_list_ttentsizeAndFlags获取目标数据(Flag标志 或者 元素占用空间大小)。ide

既然entsize_list_tt是顺序表,那么所占用内存空间必然是连续分配的。因为每一个元素都是同类型,占用相同大小的内存空间,所以能够经过索引值及首元素地址来定位到具体元素的内存地址。entsize_list_tt包含三个成员:函数

  • entsizeAndFlags:entsize 是 entry size 的缩写,所以该成员保存了元素 Flag 标志 以及 元素占用空间大小,entsizeAndFlags & ~FlagMask获取元素占用空间大小,entsizeAndFlags & FlagMask获取 Flag 标志(可指定 Flag 标志用于特殊用途);
  • count:容器的元素数量;
  • first:保存首元素,注意是首元素,不是指向首元素的指针;
define WORD_SHIFT 3UL

// 顺序表模板,其中Element为元素类型,List为定义的顺序表容器类型, FlagMask指定entsizeAndFlags成员的最低多少位
// 可用做标志位,例如0x3表示最低两位用做标志位。
template <typename Element, typename List, uint32_t FlagMask>
struct entsize_list_tt {
    uint32_t entsizeAndFlags;  //入口数量及Flag标志位
    uint32_t count;
    Element first;

    uint32_t entsize() const {
        return entsizeAndFlags & ~FlagMask;
    }
    uint32_t flags() const {
        return entsizeAndFlags & FlagMask;
    }

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

    size_t byteSize() const {
        return sizeof(*this) + (count-1)*entsize();
    }

    List *duplicate() const {
        return (List *)memdup(this, this->byteSize());
    }

    struct iterator;
    const iterator begin() const { 
        return iterator(*static_cast<const List*>(this), 0); 
    }
    iterator begin() { 
        return iterator(*static_cast<const List*>(this), 0); 
    }
    const iterator end() const { 
        return iterator(*static_cast<const List*>(this), count); 
    }
    iterator end() { 
        return iterator(*static_cast<const List*>(this), count); 
    }

    // 内嵌的迭代器定义代码
    struct iterator {

        ...

    }

};
复制代码

关于entsize_list_tt须要注意几个细节:布局

  • 容器直接保存元素,不是指向元素的指针,first成员也是元素;
  • count成员是容器包含的元素数,byteSize()返回容器占用总内存字节数,sizeOf(this)返回容器的三个成员占用的字节数,具体大小取决于 Element 占用内存大小以及 Element 的对齐结构;

下面例举一个使用entsize_list_tt模板定义的,元素大小为48字节的一个容器对象占用内存空间示例。阴影区域之间是容器所占用的连续内存空间,黄色区域表示用于保存元素的内存区域,每块黄色内存区域大小均为48字节。红色标记内存地址是元素的起始地址,begin()方法返回首元素的起始地址,end()方法返回容器的结束地址。ui

entsize_list_tt示例.jpg

注意:entsize_list_tt数据结构很是重要,方法列表、分类列表、协议列表等数据结构也是使用该模板定义。this

3、类的 ivar layout

Ivar layout 表示成员变量布局,描述每一个成员变量在对象内存空间中占用的内存区块,成员变量布局信息分散在类的instanceStartinstanceSize以及全部ivar_toffsetsizealignment_raw中。Ivar layout 也能够理解为广义上的成员变量布局。默认状况下,成员变量按照类型编码(type coding)进行自动内存对齐,例如,类的第一个成员变量是uint_32,则该成员变量offset为类的instanceStartsize4alignment_raw2

class_ro_t结构体中包含ivarLayoutweakIvarLayout成员,用于标记对象占用的内存空间中,哪些 WORD 有被用来存储id类型的成员变量,weakIvarLayout专门针对weak类型成员变量。ivarLayoutweakIvarLayout能够理解为狭义上的成员变量布局。objc_class用十六进制数表示类的 ivar layout,该十六进制数是经过压缩二进制 layout bitmap 得到。例如,调用class_getIvarLayout获取UIViewivarLayout0x0312119A12

注意:最新版本 runtime 中简化了成员变量布局过程,ivarLayoutweakIvarLayout的应用场景十分局限,仅在_class_lookUpIvar(...)查询类的成员变量时有比较重要的做用。

3.1 layout bitmap

Runtime 中用layout_bitmap结构体表示压缩前的ivarLayout,保存的是一个二进制数,二进制数每一位标记类的成员变量空间(instanceStart为起始instanceSize大小的内存空间)中,对应位置的 WORD 是否存储了id类型成员变量。例如,二进制数0101表示成员第二个、第四个成员变量是id类型。layout_bitmap包含如下成员:

  • bits:用uint8_t指针bits指向二进制数首地址;
  • bitsAllocated:表示用于保存该二进制数而分配的内存空间大小,单位bit,因为按字节读取,所以bitsAllocated一定是8的倍数;
  • bitCount:二进制数未必彻底占满bitsAllocated大小的内存空间,所以用bitCount记录,内存空间中的有效位数;
  • weak:标记是否用于描述weakIvarLayout
typedef struct {
    uint8_t *bits;
    size_t bitCount;
    size_t bitsAllocated;
    bool weak;
} layout_bitmap;
复制代码

设置成员变量对应的ivarLayoutweakIvarLayout位调用layout_bitmap_set_ivar(...)函数。其中bits参数为类的当前ivarLayoutweakIvarLayouttype参数成员变量的类型编码;offset为成员变量的offset

void
layout_bitmap_set_ivar(layout_bitmap bits, const char *type, size_t offset)
{
    size_t bit = offset / sizeof(id);

    if (!type) return;
    if (type[0] == '@'  ||  0 == strcmp(type, "^@")) {
        // id 或 id * 或 Block ("@?")
        set_bits(bits, bit, 1);
    } 
    else if (type[0] == '[') {
        // id[]
        char *t;
        unsigned long count = strtoul(type+1, &t, 10);
        if (t  &&  t[0] == '@') {
            set_bits(bits, bit, count);
        }
    } 
    else if (strchr(type, '@')) {
        _objc_inform("warning: failing to set GC layout for '%s'\n", type);
    }
}

static void set_bits(layout_bitmap bits, size_t which, size_t count)
{
    size_t bit;
    for (bit = which; bit < which + count  &&  bit < bits.bitCount; bit++) {
        bits.bits[bit/8] |= 1 << (bit % 8);
    }
    if (bit == bits.bitCount  &&  bit < which + count) {
        _objc_fatal("layout bitmap too short");
    }
}
复制代码

3.1.1 压缩 layout bitmap

类的ivarLayoutlayout_bitmap压缩后获得的十六进制数,layout_bitmap压缩调用compress_layout(...)实现。其中bits参数指向保存layout_bitmap的内存;bitmap_bits参数为二进制数的位数;weak参数表示bits数据是否为weakIvarLayout

static unsigned char *
compress_layout(const uint8_t *bits, size_t bitmap_bits, bool weak)
{
    bool all_set = YES;
    bool none_set = YES;
    unsigned char *result;

    // 多分配些额外的位
    unsigned char * const layout = (unsigned char *)
        calloc(bitmap_bits + 1, 1);
    unsigned char *l = layout;

    size_t i = 0;
    while (i < bitmap_bits) {
        // skip为本次循环二进制数连续的0位数,scan为连续的1位数
        size_t skip = 0;
        size_t scan = 0;

        while (i < bitmap_bits) {
            uint8_t bit = (uint8_t)((bits[i/8] >> (i % 8)) & 1);
            if (bit) break;
            i++;
            skip++;
        }
        while (i < bitmap_bits) {
            uint8_t bit = (uint8_t)((bits[i/8] >> (i % 8)) & 1);
            if (!bit) break;
            i++;
            scan++;
            none_set = NO;
        }

        // skip和scan的值均不能超过15,超过15则当即进行分割
        if (skip) all_set = NO;
        if (scan) none_set = NO;
        while (skip > 0xf) {
            *l++ = 0xf0;
            skip -= 0xf;
        }
        if (skip || scan) {
            *l = (uint8_t)(skip << 4);
            while (scan > 0xf) {
                *l++ |= 0x0f; 
                scan -= 0xf;
            }
            *l++ |= scan;      
        }
    }
    
    // 插入终止字节
    *l++ = '\0';
    
    if (none_set  &&  weak) {
        result = NULL;  // NULL weak layout 表示类不包含任何weak类型成员变量
    } else if (all_set  &&  !weak) {
        result = NULL;  // NULL ivar layout 表示类类全部成员变量均为`id`类型
    } else {
        result = (unsigned char *)strdup((char *)layout); 
    }
    free(layout);
    return result;
}
复制代码

总结layout_bitmap的压缩过程以下图所示:

layout_bitmap压缩步骤图解.jpg

3.1.1 解压缩 layout bitmap

调用decompress_layout(...)解压缩ivarLayout,为压缩的逆过程。例如,增长成员变量时须要更新ivarLayout,此时须要先解压ivarLayout的十六进制数获得layout_bitmap,而后更新layout_bitmap数据,最后压缩layout_bitmap获得十六进制数保存到ivarLayout。这里不作详细介绍,仅贴出相关源代码。

static void decompress_layout(const unsigned char *layout_string, layout_bitmap bits)
{
    unsigned char c;
    size_t bit = 0;
    while ((c = *layout_string++)) {
        unsigned char skip = (c & 0xf0) >> 4;
        unsigned char scan = (c & 0x0f);
        bit += skip;
        set_bits(bits, bit, scan);
        bit += scan;
    }
}
复制代码

3.2 non-fragile instance variables

Non-fragile instance variables 是内存布局的一个重要特征。当一个类的继承关系确立以后(类注册到内存后),理论上成员变量的内存布局在编译时是能够固定的,也就是说类的ivar_t成员变量的offset是能够肯定的。可是,固定offset带来坏处是,若是依赖框架升级,某基类的对象内存布局发生变化,譬如增长了成员变量,那么依赖于该框架基类的 APP 将不得不从新编译以更新offset

Runtime 引入了 non-fragile instance variables 计数以免以上问题。类的成员变量offset的值在编译时不固定,而是运行时根据父类的instanceSize动态调整(具体发生在 class realizing 阶段),固然这个过程是在类注册到内存以前完成的。若是依赖框架升级,某基类的对象内存布局发生变化,只要重启APP以触发从新注册依赖于该基类的扩展类,从而更新扩展类的成员变量offset完成类的 ivar layout 动态调整过程。

All instance variables in 64-bit Objective-C are non-fragile. That is, existing compiled code that uses a class's ivars will not break when the class or a superclass changes its own ivar layout. In particular, framework classes may add new ivars without breaking subclasses compiled against a previous version of the framework.(来自 runtime 源代码 Readme)

既然类的 ivar layout 能够运行时动态调整,是否是就意味着就能够解除类注册后才能动态添加成员变量的限制呢?其实仍是一样的问题,non-fragile instance variables 只是在类载入内存阶段动态调整了类的 ivar layout 结构,并无从新构建内存中已存在的该类的实例,内存中调整ivar layout 先后所构建的实例内存布局不一致,这显然是不容许的。Non-fragile instance variables 只能保证子类在父类扩展后能正常实例化,不能保证扩展前构建的实例仍然可用.

如下是关于 non-fragile instance variables 的具体例子的图示。如图上方,SubInstance 是类的一个实例,OldSuperInstance 是类的父类的一个实例,内存布局如图。

  • 左边采用成员变量编译时固定偏移,当父类增长 aa_new、bb_new 成员,因为类的 ivar layout 内存布局不会作相应变动,此时构建类的实例时,父类的 aa_new、bb_new 和类的 c_sub、d_sub 成员的内存空间重叠,致使不能正常构建类的实例,所以类不得不从新编译。直接体现为:使用了该类旧版本的应用、框架不能正常运行,须要使用从新编译后的更新了类的成员变量offset的新版本;

  • 左边采用 non-fragile instance variables,当父类增长 aa_new、bb_new 成员,类的 ivar layout 及 c_sub、d_sub 成员变量的offset作了相应的动态调整,类的实例能正常构建。直接体现为:使用了该类旧版本的应用、框架,在完成依赖类更新并从新启动应用后仍能正常运行。

Non-Fragile Ivar 示例.jpg

类的内存布局调整的代码主要集中在reconcileInstanceVariables(...)函数中,这里只贴出关键代码,和做必要注释,不作深刻讨论。

static void reconcileInstanceVariables(Class cls, Class supercls, const class_ro_t*& ro) 
{
    class_rw_t *rw = cls->data();

    assert(supercls);  // 不处理根类,由于没有父类就不须要调整布局
    assert(!cls->isMetaClass());    // 不处理元类,由于没有成员变量

    // Non-fragile ivars 根据父类调整类的成员变量布局
    const class_ro_t *super_ro = supercls->data()->ro;
    
    if (DebugNonFragileIvars) {
        // Non-fragile ivars 成员变量根据父类进行必要平移

        // 不处理如下类: *NSCF* classes, __CF* classes, NSConstantString, 
        // NSSimpleCString
        const char *clsname = cls->mangledName();
        if (!strstr(clsname, "NSCF")  &&  
            0 != strncmp(clsname, "__CF", 4)  &&  
            0 != strcmp(clsname, "NSConstantString")  &&  
            0 != strcmp(clsname, "NSSimpleCString")) 
        {
            uint32_t oldStart = ro->instanceStart;
            class_ro_t *ro_w = make_ro_writeable(rw);
            ro = rw->ro;
            
            // 找到类的alignment最大的成员变量,默认使用系统WORD字节数
            uint32_t alignment = 1<<WORD_SHIFT;
            if (ro->ivars) {
                for (const auto& ivar : *ro->ivars) {
                    if (ivar.alignment() > alignment) {
                        alignment = ivar.alignment();
                    }
                }
            }
            uint32_t misalignment = ro->instanceStart % alignment;
            uint32_t delta = ro->instanceStart - misalignment;
            ro_w->instanceStart = misalignment;
            ro_w->instanceSize -= delta;
            
            if (PrintIvars) {
                _objc_inform("IVARS: DEBUG: forcing ivars for class '%s' "
                             "to slide (instanceStart %zu -> %zu)", 
                             cls->nameForLogging(), (size_t)oldStart, 
                             (size_t)ro->instanceStart);
            }
            
            if (ro->ivars) {
                for (const auto& ivar : *ro->ivars) {
                    if (!ivar.offset) continue;  // anonymous bitfield
                    *ivar.offset -= delta;
                }
            }
        }
    }

    if (ro->instanceStart >= super_ro->instanceSize) {
        // 扩展后的父类`instanceSize`,没有超过原先的`instanceSize`
        return;
    }

    if (ro->instanceStart < super_ro->instanceSize) {
        // 扩展后的父类`instanceSize`,超过原先的`instanceSize`,则成员变量
        // 布局必须总体平移
        if (PrintIvars) {
            _objc_inform("IVARS: sliding ivars for class %s "
                         "(superclass was %u bytes, now %u)", 
                         cls->nameForLogging(), ro->instanceStart, 
                         super_ro->instanceSize);
        }
        class_ro_t *ro_w = make_ro_writeable(rw);  // 将ro置为可读写
        ro = rw->ro;
        moveIvars(ro_w, super_ro->instanceSize);  // 平移成员变量布局
        gdb_objc_class_changed(cls, OBJC_CLASS_IVARS_CHANGED, ro->name);  // 类变动事件,忽略
    } 
}

static void moveIvars(class_ro_t *ro, uint32_t superSize)
{
    runtimeLock.assertLocked();

    uint32_t diff;

    assert(superSize > ro->instanceStart);
    diff = superSize - ro->instanceStart;

    if (ro->ivars) {
        // 找到类的alignment最大的成员变量
        uint32_t maxAlignment = 1;
        for (const auto& ivar : *ro->ivars) {
            if (!ivar.offset) continue; 

            uint32_t alignment = ivar.alignment();
            if (alignment > maxAlignment) maxAlignment = alignment;
        }

        // 计算平移量
        uint32_t alignMask = maxAlignment - 1;
        diff = (diff + alignMask) & ~alignMask;

        // 成员变量布局平移
        for (const auto& ivar : *ro->ivars) {
            if (!ivar.offset) continue; 

            uint32_t oldOffset = (uint32_t)*ivar.offset;
            uint32_t newOffset = oldOffset + diff;
            *ivar.offset = newOffset;

            if (PrintIvars) {
                _objc_inform("IVARS: offset %u -> %u for %s "
                             "(size %u, align %u)", 
                             oldOffset, newOffset, ivar.name, 
                             ivar.size, ivar.alignment());
            }
        }
    }

    *(uint32_t *)&ro->instanceStart += diff;
    *(uint32_t *)&ro->instanceSize += diff;
}
复制代码

4、成员变量的应用

4.1 查询成员变量信息

ivarLayoutweakIvarLayout能够体如今_class_lookUpIvar(...)函数代码中,_class_lookUpIvar(...)用于查询目标成员变量偏移量offset和内存管理方式。ivarLayoutweakIvarLayout用于判断成员变量的内存管理方式。ivarLayout中标记为scan的成员变量的内存管理方式为strongweakIvarLayout中标记为scan的成员变量的内存管理方式为weak

// 成员变量的内存管理方式,包括:未知/strong/weak/assign。
typedef enum {
    objc_ivar_memoryUnknown,     // unknown / unknown
    objc_ivar_memoryStrong,      // direct access / objc_storeStrong
    objc_ivar_memoryWeak,        // objc_loadWeak[Retained] / objc_storeWeak
    objc_ivar_memoryUnretained   // direct access / direct access
} objc_ivar_memory_management_t;

static void 
_class_lookUpIvar(Class cls, Ivar ivar, ptrdiff_t& ivarOffset, 
                  objc_ivar_memory_management_t& memoryManagement)
{
    ivarOffset = ivar_getOffset(ivar);
    
    // 查找 ARC variables and ARC-style weak.
    bool hasAutomaticIvars = NO;
    for (Class c = cls; c; c = c->superclass) {
        if (c->hasAutomaticIvars()) {
            hasAutomaticIvars = YES;
            break;
        }
    }

    if (hasAutomaticIvars) {
        Class ivarCls = _class_getClassForIvar(cls, ivar);
        if (ivarCls->hasAutomaticIvars()) {
            ptrdiff_t localOffset = 
                ivarOffset - ivarCls->alignedInstanceStart();

            // 判断ivarLayout中成员变量offset对应的WORD是否保存id类型
            if (isScanned(localOffset, class_getIvarLayout(ivarCls))) {
                memoryManagement = objc_ivar_memoryStrong;
                return;
            }
            
            // 判断weakIvarLayout中成员变量offset对应的WORD是否保存id类型
            if (isScanned(localOffset, class_getWeakIvarLayout(ivarCls))) {
                memoryManagement = objc_ivar_memoryWeak;
                return;
            }

            // assign仅在ARC下才有效
            if (ivarCls->isARC()) {
                memoryManagement = objc_ivar_memoryUnretained;
                return;
            }
        }
    }
    
    memoryManagement = objc_ivar_memoryUnknown;
}

static bool isScanned(ptrdiff_t ivar_offset, const uint8_t *layout) 
{
    if (!layout) return NO;

    ptrdiff_t index = 0, ivar_index = ivar_offset / sizeof(void*);
    uint8_t byte;
    while ((byte = *layout++)) {
        unsigned skips = (byte >> 4);
        unsigned scans = (byte & 0x0F);
        index += skips;
        if (index > ivar_index) return NO;
        index += scans;
        if (index > ivar_index) return YES;
    }
    return NO;
}
复制代码

4.2 类添加成员变量

用于添加成员变量class_addIvar(...)函数源代码有助于理解ivar_t几个成员变量的含义。

// class allocated but not yet registered
#define RW_CONSTRUCTING (1<<26)
// class allocated and registered
#define RW_CONSTRUCTED (1<<25)

// 添加成员变量
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());

    // 元类不存在成员变量
    if (cls->isMetaClass()) {
        return NO;
    }

    // 仅在正在构建阶段的类才能够添加成员变量
    if (!(cls->data()->flags & RW_CONSTRUCTING)) {
        return NO;
    }

    // 匿名或不存在的成员变量才可添加。注意:不支持沿继承链搜索
    if ((name  &&  getIvar(cls, name))  ||  size > UINT32_MAX) {
        return NO;
    }

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

    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 {
        // 成员变量列表为空,直接分配ivar_list_t结构体占用空间的内存(只有首元素)
        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++);
    ivar.offset = (int32_t *)malloc(sizeof(int32_t));
    // 更新新增成员变量的偏移量
    *ivar.offset = offset;  
    ivar.name = name ? strdup(name) : nil;
    ivar.type = strdup(type);
    ivar.alignment_raw = alignment;
    ivar.size = (uint32_t)size;

    // 成员变量列表指向新成员变量列表
    ro_w->ivars = newlist;
    cls->setInstanceSize((uint32_t)(offset + size));

    return YES;
}
复制代码

4.3 获取对象成员变量的值

构建类的实例时,按照成员变量列表分配内存空间,在访问实例的成员变量的值时,实例的类的成员变量列表起到相当重要的做用。调用object_getIvar(id obj, Ivar ivar)函数获取对象的成员变量。获取对象成员变量的值的大体流程是:

  • 根据对象isa指针获取对象的类;
  • 从类的成员变量列表中查询该成员变量的偏移量offset
  • 成员变量的值的地址 = 对象的起始地址 + 偏移量;

代码中,首先调用_class_lookUpIvar(...)查询成员变量信息,返回成员变量的偏移量offset和内存管理方式memoryManagement用于判断成员变量是否为weak类型;判断成员变量为 weak 时,调用id objc_loadWeak(id *location)获取成员变量的值,之因此这样处理是由于须要将返回的弱引用所指向的对象添加到 Autorelease Pool 中以保证对象能在弱引用的做用域内维持而不被当即释放。

id object_getIvar(id obj, Ivar ivar)
{
    if (!obj  ||  !ivar  ||  obj->isTaggedPointer()) return nil;

    ptrdiff_t offset;
    objc_ivar_memory_management_t memoryManagement;
    _class_lookUpIvar(obj->ISA(), ivar, offset, memoryManagement);

    id *location = (id *)((char *)obj + offset);

    if (memoryManagement == objc_ivar_memoryWeak) {
        return objc_loadWeak(location);
    } else {
        return *location;
    }
}
复制代码

5、总结

  • 成员变量记录了成员变量的变量名,还记录了成员变量在对象内存空间中的偏移量、成员变量数据类型、占用字节数以及对齐字节数,用ivarLayoutweakIvarLayout记录成员变量的内存管理方式;

  • 新版本 runtime 支持 non-fragile instance variables,成员变量的偏移量并非编译时固定,而是在运行时根据父类的instanceSize动态调整;

  • ivarLayoutweakIvarLayout数据形式时十六进制数,是对layout_bitmapbits保存的二进制数压缩处理后的结果,layout_bitmap保存的二进制数记录了类对象内存空间中的instanceStart起始的类的成员变量内存空间中哪一个 WORD 保存了id类型成员变量;

  • ivarLayoutweakIvarLayout记录了某 WORD 保存id,则二进制该位置为1,称该对象中该成员变量scannedivarLayout中标记了scanned的成员变量内存管理方式为strongweakIvarLayout中标记了scanned的成员变量内存管理方式为weak

  • 下一篇介绍方法列表的实现。

相关文章
相关标签/搜索