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

  • 下一篇介紹方法列表的實現。

相關文章
相關標籤/搜索