runtime之ivar內存佈局篇

隨着runtime愈來愈經常使用,iOSerruntime的理解要求也愈來愈高,你們都熱衷於runtime源碼理解,這篇我帶領你們理解下關於Ivar的內容。數組

1.內存對齊

在分析Ivar以前,咱們要了解下內存對齊的概念。 每一個特定平臺上的編譯器都有本身的默認「對齊係數」,而64位中iOS裏這個參數是8。咱們測試一下:bash

@interface Dog : NSObject
{
    int age;         //4個字節
    BOOL sex;        //1個字節
    NSString* name;  //8個字節的指針地址
    short lifeTime;      //2個字節
    NSString* style;    //8個字節的指針地址
}
@end
複製代碼

這是咱們新建的類Dog,裏面有各類各樣的成員變量,若是不存在內存對齊的話,會是一段連續的地址。咱們打印下成員變量的地址偏移:佈局

Class class = objc_getClass("Dog");
    NSLog(@"內存地址:%p",class);
    unsigned int count;
    Ivar* ivars =class_copyIvarList(objc_getClass("Dog"), &count);
    for (unsigned int i = 0; i < count; i++) {
        Ivar ivar = ivars[i];
        ptrdiff_t offset = ivar_getOffset(ivar);
        NSLog(@"%s = %td",ivar_getName(ivar),offset);
    }
    free(ivars);
    NSLog(@"Dog總字節 = %lu",class_getInstanceSize(objc_getClass("Dog")));
複製代碼

運行結果:測試

2019-03-02 14:26:53.613593+0800 Runtime-Ivar[39894:1319445] 內存地址:0x1060d6f28
2019-03-02 14:26:53.613780+0800 Runtime-Ivar[39894:1319445] age = 8
2019-03-02 14:26:53.613867+0800 Runtime-Ivar[39894:1319445] sex = 12
2019-03-02 14:26:53.613954+0800 Runtime-Ivar[39894:1319445] name = 16
2019-03-02 14:26:53.614038+0800 Runtime-Ivar[39894:1319445] lifeTime = 24
2019-03-02 14:26:53.614123+0800 Runtime-Ivar[39894:1319445] style = 32
2019-03-02 14:26:53.614234+0800 Runtime-Ivar[39894:1319445] Dog總字節 = 40
複製代碼

根據打印結果,sex是bool類型,應該只佔1個字節,可是卻好像佔了4個字節,其實這裏並非佔了4個字節,而是由於內存對齊,其中3個字節是沒用的。咱們畫下內存結構圖: ui

Dog的ivar內存分佈
咱們能夠看到內存並非所有佔滿的,這是因爲CPU並非以字節爲單位存取數據的,以單字節爲單位會致使效率變差,開銷變大,因此 CPU 通常會以 2/4/8/16/32 字節爲單位來進行存取操做。而這裏,會以8個字節爲單位存取。

2.ivar的內存分佈

這一部分咱們從這4個方面去看ivar的分佈狀況。this

  • 屬性與變量的分佈
@interface Cat : NSObject
{
    NSString* c1;
    NSString* c4;
}

@property(nonatomic, copy)NSString* c2;
@property(nonatomic, copy)NSString* c3;
@end
複製代碼
unsigned int count;
    Ivar* ivars =class_copyIvarList(objc_getClass("Cat"), &count);
    for (unsigned int i = 0; i < count; i++) {
        Ivar ivar = ivars[i];
        ptrdiff_t offset = ivar_getOffset(ivar);
        NSLog(@"%s = %td",ivar_getName(ivar),offset);
    }
    free(ivars);
    NSLog(@"Cat總字節 = %lu",class_getInstanceSize(objc_getClass("Cat")));
複製代碼

運行結果:atom

2019-03-01 17:19:27.926009+0800 Runtime-Ivar[10017:6532014] c1 = 8
2019-03-01 17:19:27.926046+0800 Runtime-Ivar[10017:6532014] c4 = 16
2019-03-01 17:19:27.926056+0800 Runtime-Ivar[10017:6532014] _c2 = 24
2019-03-01 17:19:27.926065+0800 Runtime-Ivar[10017:6532014] _c3 = 32
2019-03-01 17:19:27.926097+0800 Runtime-Ivar[10017:6532014] Cat總字節 = 40
複製代碼

咱們能夠看到先是成員變量後是屬性spa

  • 對象類型與基本類型的分佈 先看下屬性吧:
@interface Cat : NSObject

@property(nonatomic, copy)NSString* c1;
@property(nonatomic, assign)int c3;
@property(nonatomic, copy)NSString* c2;
@property(nonatomic, assign)int c4;
@end
複製代碼

打印ivar的方法和上面一致,運行後:3d

2019-03-01 17:11:58.167160+0800 Runtime-Ivar[9888:6528420] _c3 = 8
2019-03-01 17:11:58.167202+0800 Runtime-Ivar[9888:6528420] _c4 = 12
2019-03-01 17:11:58.167214+0800 Runtime-Ivar[9888:6528420] _c1 = 16
2019-03-01 17:11:58.167224+0800 Runtime-Ivar[9888:6528420] _c2 = 24
2019-03-01 17:11:58.167264+0800 Runtime-Ivar[9888:6528420] Cat總字節 = 32
複製代碼

咱們能夠看到屬性的話先是基本類型後是對象類型。 再看下成員變量吧:指針

@interface Cat : NSObject
{
    NSString* c1;
    int c3;
    NSString* c2;
    int c4;
}
@end
複製代碼

運行結果:

2019-03-01 17:13:05.937474+0800 Runtime-Ivar[9909:6529050] c1 = 8
2019-03-01 17:13:05.937515+0800 Runtime-Ivar[9909:6529050] c3 = 16
2019-03-01 17:13:05.937526+0800 Runtime-Ivar[9909:6529050] c2 = 24
2019-03-01 17:13:05.937534+0800 Runtime-Ivar[9909:6529050] c4 = 32
2019-03-01 17:13:05.937567+0800 Runtime-Ivar[9909:6529050] Cat總字節 = 40
複製代碼

咱們能夠看到成員變量的話沒有前後之分。

  • m文件與h文件的分佈 先看屬性吧:
Cat.h
@interface Cat : NSObject

@property(nonatomic, copy)NSString* c1;
@property(nonatomic, copy)NSString* c3;

@end
Cat.m
#import "Cat.h"
@interface Cat()
@property(nonatomic, copy)NSString* c2;
@property(nonatomic, copy)NSString* c4;
@end
@implementation Cat
@end
複製代碼

運行結果:

2019-03-01 17:16:16.989271+0800 Runtime-Ivar[9962:6530367] _c1 = 8
2019-03-01 17:16:16.989309+0800 Runtime-Ivar[9962:6530367] _c3 = 16
2019-03-01 17:16:16.989319+0800 Runtime-Ivar[9962:6530367] _c2 = 24
2019-03-01 17:16:16.989328+0800 Runtime-Ivar[9962:6530367] _c4 = 32
2019-03-01 17:16:16.989360+0800 Runtime-Ivar[9962:6530367] Cat總字節 = 40
複製代碼

咱們能夠看到先是h文件後是m文件。 再看當作員變量:

Cat.h
@interface Cat : NSObject
{
    NSString* c1;
    NSString* c3;
}
@end
Cat.m
#import "Cat.h"
@interface Cat()
{
    NSString* c2;
    NSString* c4;
}
@end
@implementation Cat
@end
複製代碼

運行結果:

2019-03-01 17:18:05.865890+0800 Runtime-Ivar[9992:6531268] c1 = 8
2019-03-01 17:18:05.865942+0800 Runtime-Ivar[9992:6531268] c3 = 16
2019-03-01 17:18:05.865952+0800 Runtime-Ivar[9992:6531268] c2 = 24
2019-03-01 17:18:05.865960+0800 Runtime-Ivar[9992:6531268] c4 = 32
2019-03-01 17:18:05.866000+0800 Runtime-Ivar[9992:6531268] Cat總字節 = 40
複製代碼

和上面同樣顯示h文件後是m文件。

那咱們綜合以上幾種狀況:

Cat.h
@interface Cat : Animal
{
    NSString* string_h_ivar;
    int int_h_ivar;

}

@property(nonatomic, copy)NSString* string_h_property;
@property(nonatomic, assign)int int_h_property;

@end
Cat.m
#import "Cat.h"
@interface Cat()
{
    NSString* string_m_ivar;
    int int_m_ivar;
    
}
@property(nonatomic, assign)int int_m_property;
@property(nonatomic, copy)NSString* string_m_property;
@end

@implementation Cat

@end
複製代碼

運行結果爲:

2019-03-01 16:43:56.295851+0800 Runtime-Ivar[9412:6514675] string_h_ivar = 24
2019-03-01 16:43:56.295907+0800 Runtime-Ivar[9412:6514675] int_h_ivar = 32
2019-03-01 16:43:56.295917+0800 Runtime-Ivar[9412:6514675] string_m_ivar = 40
2019-03-01 16:43:56.295926+0800 Runtime-Ivar[9412:6514675] int_m_ivar = 48
2019-03-01 16:43:56.295934+0800 Runtime-Ivar[9412:6514675] _int_h_property = 52
2019-03-01 16:43:56.295942+0800 Runtime-Ivar[9412:6514675] _int_m_property = 56
2019-03-01 16:43:56.295950+0800 Runtime-Ivar[9412:6514675] _string_h_property = 64
2019-03-01 16:43:56.295960+0800 Runtime-Ivar[9412:6514675] _string_m_property = 72
2019-03-01 16:43:56.296001+0800 Runtime-Ivar[9412:6514675] Cat總字節 = 80
複製代碼

分析可得順序爲h文件的ivar->m文件的ivar->h文件的property基本類型->m文件的property對象類型

3.分析ivarlayout源碼

runtime.h裏面關於IvarLayout的幾個方法。

const uint8_t * _Nullable
class_getIvarLayout(Class _Nullable cls)
OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);

const uint8_t * _Nullable
class_getWeakIvarLayout(Class _Nullable cls)
OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);

void
class_setIvarLayout(Class _Nullable cls, const uint8_t * _Nullable layout)
OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);

void
class_setWeakIvarLayout(Class _Nullable cls, const uint8_t * _Nullable layout)
OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
複製代碼

咱們試用下,咱們建立Person類:

@interface Person : NSObject
{
    int int1;
    bool bool1;
    __strong NSString* strong1;
    __weak NSString* weak1;
    char char1;
    __weak NSString* weak2;
     __strong NSString* strong2;
     __strong NSString* strong3;
    char char2;
    __weak NSString* weak3;
    char char3;
    int  int2;
    __weak NSString* weak4;
    __weak NSString* weak5;
}
複製代碼

而後咱們使用下class_getIvarLayoutclass_getWeakIvarLayout

-(void)getIvarLayout {
    const uint8_t *strongLayout =   class_getIvarLayout(objc_getClass("Person"));
    if (!strongLayout) {
        return;
    }
    uint8_t byte;
    while ((byte = *strongLayout++)) {
        printf("strongLayout = #%02x\n",byte);
    }
    
    const uint8_t *weakLayout =   class_getWeakIvarLayout(objc_getClass("Person"));
    if (!weakLayout) {
        return;
    }
    while ((byte = *weakLayout++)) {
        printf("weakLayout = #%02x\n",byte);
    }
}
複製代碼

打印結果:

strongLayout = #11
strongLayout = #32
weakLayout = #21
weakLayout = #11
weakLayout = #31
weakLayout = #12
複製代碼

粗一看看不出什麼,文檔裏面並無詳細說明layout的含義,咱們要探究IvarLayout的話,仍是要在源碼找線索。

void fixupCopiedIvars(id newObject, id oldObject)
{
    for (Class cls = oldObject->ISA(); cls; cls = cls->superclass) {
        if (cls->hasAutomaticIvars()) {
            // Use alignedInstanceStart() because unaligned bytes at the start
            // of this class's ivars are not represented in the layout bitmap. size_t instanceStart = cls->alignedInstanceStart(); const uint8_t *strongLayout = class_getIvarLayout(cls); if (strongLayout) { id *newPtr = (id *)((char*)newObject + instanceStart); unsigned char byte; while ((byte = *strongLayout++)) { unsigned skips = (byte >> 4); unsigned scans = (byte & 0x0F); newPtr += skips; while (scans--) { // ensure strong references are properly retained. id value = *newPtr++; if (value) objc_retain(value); } } } const uint8_t *weakLayout = class_getWeakIvarLayout(cls); // fix up weak references if any. if (weakLayout) { id *newPtr = (id *)((char*)newObject + instanceStart), *oldPtr = (id *)((char*)oldObject + instanceStart); unsigned char byte; while ((byte = *weakLayout++)) { unsigned skips = (byte >> 4); unsigned weaks = (byte & 0x0F); newPtr += skips, oldPtr += skips; while (weaks--) { objc_copyWeak(newPtr, oldPtr); ++newPtr, ++oldPtr; } } } } } } 複製代碼

這一段源碼是runtime如何使用strongLayoutweakLayout。 下面,咱們仔仔細細的分析這段源碼,咱們取出其中關鍵的一部分,先看關於strongLayout:

//得到strongLayout的數組,數組元素類型爲uint8_t,uint8_t爲2位16進制數
const uint8_t *strongLayout = class_getIvarLayout(cls);
if (strongLayout) {
    //newPtr爲ivar的初始地址
    id *newPtr = (id *)((char*)newObject + instanceStart);
    unsigned char byte;
    //遍歷strongLayout,而且將內容賦值給byte
    while ((byte = *strongLayout++)) {
        //取出byte的左邊一位
        unsigned skips = (byte >> 4);
        //取出byte的右邊一位
        unsigned scans = (byte & 0x0F);
        //地址跳過skips位
        newPtr += skips;
        //循環scans次
        while (scans--) {
            // ensure strong references are properly retained.
            //取出地址裏的內容,而且地址+1
            id value = *newPtr++;
            if (value) objc_retain(value);
        }
    }
}
複製代碼

從這一段源碼,咱們能夠看到scans的地址值存放的是strong的成員變量,而skips是無效值,一樣咱們也能夠分析weakLayout的那一段源碼。爲了能更加清晰的看到ivar的佈局,咱們經過ivar_getOffset方法得到ivar的內存佈局。

-(void)getOffset {
    unsigned int count;
    Ivar* ivars =class_copyIvarList(objc_getClass("Person"), &count);
    for (unsigned int i = 0; i < count; i++) {
        Ivar ivar = ivars[i];
        ptrdiff_t offset = ivar_getOffset(ivar);
        NSLog(@"%s = %td",ivar_getName(ivar),offset);
    }
    free(ivars);
    NSLog(@"Person總字節 = %lu",class_getInstanceSize(objc_getClass("Person")));
}
複製代碼

運行結果:

2019-03-04 10:27:23.147813+0800 Runtime-Ivar[32952:841600] int1 = 8
2019-03-04 10:27:23.147842+0800 Runtime-Ivar[32952:841600] bool1 = 12
2019-03-04 10:27:23.147853+0800 Runtime-Ivar[32952:841600] strong1 = 16
2019-03-04 10:27:23.147863+0800 Runtime-Ivar[32952:841600] weak1 = 24
2019-03-04 10:27:23.147875+0800 Runtime-Ivar[32952:841600] char1 = 32
2019-03-04 10:27:23.147884+0800 Runtime-Ivar[32952:841600] weak2 = 40
2019-03-04 10:27:23.147894+0800 Runtime-Ivar[32952:841600] strong2 = 48
2019-03-04 10:27:23.147904+0800 Runtime-Ivar[32952:841600] strong3 = 56
2019-03-04 10:27:23.147913+0800 Runtime-Ivar[32952:841600] char2 = 64
2019-03-04 10:27:23.147922+0800 Runtime-Ivar[32952:841600] weak3 = 72
2019-03-04 10:27:23.147932+0800 Runtime-Ivar[32952:841600] char3 = 80
2019-03-04 10:27:23.147941+0800 Runtime-Ivar[32952:841600] int2 = 84
2019-03-04 10:27:23.147950+0800 Runtime-Ivar[32952:841600] weak4 = 88
2019-03-04 10:27:23.147959+0800 Runtime-Ivar[32952:841600] weak5 = 96
2019-03-04 10:27:23.147992+0800 Runtime-Ivar[32952:841600] Cat總字節 = 104
複製代碼

經過這個咱們能夠畫出內存佈局圖:

ivar的內存佈局圖
這張圖能夠清晰的看到內存佈局,而後咱們再把 strongLayoutweakLayout加到上面去:
加入layout佈局
由於不管是 strong仍是 weak都是對象類型的變量,存的都是指針地址,因此佔8位。因此源碼中的 scan地址,其實就是存的 strong或者 weak的指針地址。咱們能夠看到在 strongLayout中,高位x8表明非 strong類型所佔的內存地址,低位表明 strong類型的個數,在 weakLayout中,高位x8表明非 weak類型所佔的內存地址,低位表明 weak類的個數。
相關文章
相關標籤/搜索