iOS對(duì)象的底層原理探索(下)

上一篇 介紹了alloc的調(diào)用順序和init及new的底層邏輯。這一篇將帶大家繼續(xù)了解對(duì)象的內(nèi)存分布南蓬。

影響對(duì)象內(nèi)存的因素

對(duì)象??存儲(chǔ)了?個(gè)isa指針 + 成員變量的值藻治,\color{red}{isa指針是固定的双藕,占8個(gè)字節(jié)}蠢甲,所以影響對(duì)象內(nèi)存的只有成員變量(屬性會(huì)?動(dòng)?成帶下劃線的成員變量)

@interface LGPerson : NSObject
//isa -- 48 
@property (nonatomic ,copy) NSString *name;
@property (nonatomic ,copy) NSString *hobby;
@property (nonatomic ,assign) int age;
@property (nonatomic ,assign) double hight;
@property (nonatomic ,assign) short number;
@property (nonatomic ,assign) char a;

@end

LGPerson *p = [[LGPerson alloc] init];
p.name      = @"Vitus";
p.age       = 67;
NSLog(@"對(duì)象至少需要的內(nèi)存大小--%lu",class_getInstanceSize([p class])); //40
NSLog(@"系統(tǒng)分配的內(nèi)存大小--%lu",malloc_size((__bridge const void *)(p))); //48

打印輸出結(jié)果呻澜,LGPerson實(shí)際占用內(nèi)存是40字節(jié)纠屋,而系統(tǒng)分配給它的內(nèi)存是48個(gè)字節(jié)涂臣。


image.png

class_getInstanceSize()執(zhí)行流程圖:


image.png

接下來(lái),我們給LGPerson分別添加一個(gè)實(shí)例方法售担,一個(gè)類方法赁遗,看對(duì)它的內(nèi)存有沒(méi)有影響。

@interface LGPerson : NSObject
...
- (void)test;
+ (void)test;

@end

@implementation LGPerson

- (void)test {
    NSLog(@"%s",__func__);
}

+ (void)test {
    NSLog(@"%s",__func__);
}
@end

編譯項(xiàng)目代碼族铆,發(fā)現(xiàn)輸出結(jié)果并沒(méi)有變化岩四,說(shuō)明給對(duì)象添加實(shí)例方法和類方法對(duì)它的內(nèi)存并沒(méi)有任何影響。那么這個(gè)現(xiàn)象出現(xiàn)的原因是什么呢哥攘?

其實(shí)是因?yàn)槠驶停谖覀兊膶?shí)例對(duì)象中材鹦,實(shí)際存儲(chǔ)的是對(duì)象的isa指針和實(shí)例對(duì)象的成員變量的具體的值。

image.png

蘋果會(huì)自動(dòng)重排成員變量的順序來(lái)達(dá)到一個(gè)內(nèi)存優(yōu)化的目的耕姊,不滿8字節(jié)的成員變量的屬性桶唐,會(huì)把其對(duì)應(yīng)的值存放在一個(gè)8字節(jié)的內(nèi)存空間里面。

聯(lián)合體

聯(lián)合體?叫共?體茉兰,union就是在內(nèi)存中劃了?個(gè)?夠?的空間尤泽,聯(lián)合體的成員變量就相當(dāng)于為這塊內(nèi)存空間開(kāi)辟了?個(gè)訪問(wèn)途徑,他們共享這?塊內(nèi)存邦邦。
聯(lián)合體的??計(jì)算奉?倆個(gè)規(guī)則
1.聯(lián)合體??必須能容納聯(lián)合體中最?的成員變量
2.通過(guò)1計(jì)算出的聯(lián)合體??必須是聯(lián)合體中占內(nèi)存??最?的基本數(shù)據(jù)類型??的整數(shù)倍

struct Teacher1 {
    char *name;
    int age;
    double height;
}t1;

NSLog(@"name=%s, age=%d, height=%f", t1.name, t1.age, t1.height); // name=(null), age=0, height=0.000000
t1.name = "安安老師";
NSLog(@"name=%s, age=%d, height=%f", t1.name, t1.age, t1.height); // name=安安老師, age=0, height=0.000000
t1.age = 18;
NSLog(@"name=%s, age=%d, height=%f", t1.name, t1.age, t1.height); // name=安安老師, age=18, height=0.000000
t1.height = 1.80;
NSLog(@"name=%s, age=%d, height=%f", t1.name, t1.age, t1.height); // name=安安老師, age=18, height=1.800000
// 0x100008508: 0x100008508 -- 0x100008510 -- 0x100008518
NSLog(@"%p: %p -- %p -- %p", &t1, &t1.name, &t1.age, &t1.height); 

// 聯(lián)合體
union Teacher2 {
    char *name;  // 8字節(jié)
    int age;
    double height;
}t2;

t2.name = "安安老師";
t2.age = 18;
t2.height = 1.80;
// 0x100008508: 0x100008508 -- 0x100008508 -- 0x100008508
NSLog(@"%p: %p -- %p -- %p", &t2, &t2.name, &t2.age, &t2.height);

聯(lián)合體的所有成員變量共用同一個(gè)內(nèi)存地址安吁,賦值了一個(gè)成員會(huì)影響別的不同類型成員的取值。
聯(lián)合體的大小決定于最大成員(基本數(shù)據(jù)類型的整數(shù)倍燃辖,數(shù)組不是基本數(shù)據(jù)類型)(t2最大的是char * 相當(dāng)于是對(duì)象8個(gè)字節(jié))鬼店。

union Teahcer3 {
    char a[7]; // 占7字節(jié)
    int b; // 占4字節(jié)
}t3;  // 4字節(jié)的整數(shù)倍,至少需要8字節(jié)

結(jié)構(gòu)體與聯(lián)合體的區(qū)別:
結(jié)構(gòu)體(struct)中所有變量是“共存”的黔龟,?聯(lián)合體(union)中是各變量是“互斥”的妇智,只能存在?個(gè)。
struct內(nèi)存空間的分配是粗放的氏身,不管?不?巍棱,全部分配。這樣帶來(lái)的?個(gè)壞處就是對(duì)于內(nèi)存的消耗要??些蛋欣。但是結(jié)構(gòu)體??的數(shù)據(jù)是完整的航徙。
聯(lián)合體??的數(shù)據(jù)只能存在?個(gè),但優(yōu)點(diǎn)是內(nèi)存使?更為精細(xì)靈活陷虎,也節(jié)省了內(nèi)存空間到踏。

位域

struct LGStruct1 {
    char a;
    char b;
    char c;
    char d;
}struct1;


struct LGStruct2 {
    // a: 位域名  32:位域長(zhǎng)度
    int a : 32;
    char b : 2;
    char c : 7;
    char d : 2;
}struct2;

struct LGStruct3 {
    // a: 位域名  32:位域長(zhǎng)度
    char a : 1;
    char b : 1;
    char c : 1;
    char d : 1;
}struct3;

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        //4,1,4
        NSLog(@"%ld,%ld,%ld",sizeof(struct1),sizeof(struct2),sizeof(struct3));
    }
    return 0;
}

image.png

char a : 1;//位域這里的1表示的是位域的信息,用位域表示信息需要注意兩點(diǎn):

位域的長(zhǎng)度不能超過(guò)數(shù)據(jù)類型的最大長(zhǎng)度尚猿。
一個(gè)位域是存儲(chǔ)在同一個(gè)內(nèi)存空間窝稿,如果當(dāng)前位域的空間不夠存儲(chǔ),會(huì)從下一個(gè)字節(jié)開(kāi)始存儲(chǔ)凿掂,如struct2就是這樣的情況伴榔。

nonPointerIsa

nonPointerIsa是內(nèi)存優(yōu)化的?種?段。isa是?個(gè)Class類型的結(jié)構(gòu)體指針庄萎,占8個(gè)字節(jié)踪少,主要是?
來(lái)存內(nèi)存地址的。但是8個(gè)字節(jié)意味著它就有8*8=64位糠涛。存儲(chǔ)地址根本不需要這么多的內(nèi)存空間援奢。
?且每個(gè)對(duì)象都有個(gè)isa指針,這樣就浪費(fèi)了內(nèi)存脱羡。所以蘋果就把和對(duì)象?些息息相關(guān)的東?萝究,存
在了這塊內(nèi)存空間??免都。這種isa指針就叫nonPointerIsa。

isa詳解

  • 在arm64架構(gòu)之前帆竹,isa就是一個(gè)普通的指針绕娘,存儲(chǔ)著Class、MetaClass對(duì)象的內(nèi)存地址栽连。
  • 從arm64架構(gòu)開(kāi)始险领,對(duì)isa進(jìn)行了優(yōu)化,變成了一個(gè)共用體(union)結(jié)構(gòu)秒紧,還使用位域來(lái)存儲(chǔ)更多信息绢陌。
    來(lái)看看isa_t的聲明,它是一個(gè)聯(lián)合體:
#include "isa.h"

union isa_t {
    isa_t() { }
    isa_t(uintptr_t value) : bits(value) { }

    uintptr_t bits;

private:
    // Accessing the class requires custom ptrauth operations, so
    // force clients to go through setClass/getClass by making this
    // private.
    Class cls;

public:
#if defined(ISA_BITFIELD)
    struct {
        ISA_BITFIELD;  // defined in isa.h
    };

    bool isDeallocating() {
        return extra_rc == 0 && has_sidetable_rc == 0;
    }
    void setDeallocating() {
        extra_rc = 0;
        has_sidetable_rc = 0;
    }
#endif

    void setClass(Class cls, objc_object *obj);
    Class getClass(bool authenticated);
    Class getDecodedClass(bool authenticated);
};
# if __arm64__
// ARM64 simulators have a larger address space, so use the ARM64e
// scheme even when simulators build for ARM64-not-e.
#   if __has_feature(ptrauth_calls) || TARGET_OS_SIMULATOR
#     define ISA_MASK        0x007ffffffffffff8ULL
#     define ISA_MAGIC_MASK  0x0000000000000001ULL
#     define ISA_MAGIC_VALUE 0x0000000000000001ULL
#     define ISA_HAS_CXX_DTOR_BIT 0
#     define ISA_BITFIELD                                                      \
        uintptr_t nonpointer        : 1;                                       \
        uintptr_t has_assoc         : 1;                                       \
        uintptr_t weakly_referenced : 1;                                       \
        uintptr_t shiftcls_and_sig  : 52;                                      \
        uintptr_t has_sidetable_rc  : 1;                                       \
        uintptr_t extra_rc          : 8
#     define RC_ONE   (1ULL<<56)
#     define RC_HALF  (1ULL<<7)
#   else
#     define ISA_MASK        0x0000000ffffffff8ULL
#     define ISA_MAGIC_MASK  0x000003f000000001ULL
#     define ISA_MAGIC_VALUE 0x000001a000000001ULL
#     define ISA_HAS_CXX_DTOR_BIT 1
#     define ISA_BITFIELD                                                      \
        uintptr_t nonpointer        : 1;                                       \
        uintptr_t has_assoc         : 1;                                       \
        uintptr_t has_cxx_dtor      : 1;                                       \
        uintptr_t shiftcls          : 33; /*MACH_VM_MAX_ADDRESS 0x1000000000*/ \
        uintptr_t magic             : 6;                                       \
        uintptr_t weakly_referenced : 1;                                       \
        uintptr_t unused            : 1;                                       \
        uintptr_t has_sidetable_rc  : 1;                                       \
        uintptr_t extra_rc          : 19
#     define RC_ONE   (1ULL<<45)
#     define RC_HALF  (1ULL<<18)
#   endif

# elif __x86_64__
#   define ISA_MASK        0x00007ffffffffff8ULL
#   define ISA_MAGIC_MASK  0x001f800000000001ULL
#   define ISA_MAGIC_VALUE 0x001d800000000001ULL
#   define ISA_HAS_CXX_DTOR_BIT 1
#   define ISA_BITFIELD                                                        \
      uintptr_t nonpointer        : 1;                                         \
      uintptr_t has_assoc         : 1;                                         \
      uintptr_t has_cxx_dtor      : 1;                                         \
      uintptr_t shiftcls          : 44; /*MACH_VM_MAX_ADDRESS 0x7fffffe00000*/ \
      uintptr_t magic             : 6;                                         \
      uintptr_t weakly_referenced : 1;                                         \
      uintptr_t unused            : 1;                                         \
      uintptr_t has_sidetable_rc  : 1;                                         \
      uintptr_t extra_rc          : 8
#   define RC_ONE   (1ULL<<56)
#   define RC_HALF  (1ULL<<7)

nonPointerIsa里64位域里的內(nèi)容

名稱 含義
nonpointer 表示是否對(duì) isa 指針開(kāi)啟指針優(yōu)化熔恢。0:純isa指針脐湾,1:不止是類對(duì)象地址,isa 中包含了類信息、對(duì)象的引用計(jì)數(shù)等
has_assoc 關(guān)聯(lián)對(duì)象標(biāo)志位叙淌,0沒(méi)有秤掌,1存在
has_cxx_dtor 該對(duì)象是否有 C++ 或者 Objc 的析構(gòu)器,如果有析構(gòu)函數(shù),則需要做析構(gòu)邏輯鹰霍,如果沒(méi)有闻鉴,則可以更快的釋放對(duì)象。有成員變量就有c++析構(gòu)函數(shù)茂洒。
shiftcls 存儲(chǔ)類指針的值孟岛。開(kāi)啟指針優(yōu)化的情況下,在 arm64 架構(gòu)(真機(jī))中有 33 位用來(lái)存儲(chǔ)類指針督勺。
magic 用于調(diào)試器判斷當(dāng)前對(duì)象是真的對(duì)象還是沒(méi)有初始化的空間
weakly_referenced 對(duì)象是否被指向或者曾經(jīng)指向一個(gè) ARC 的弱變量渠羞,沒(méi)有弱引用的對(duì)象可以更快釋放。(是否被__weak修飾)
deallocating 標(biāo)志對(duì)象是否正在釋放內(nèi)存
has_sidetable_rc 是否需要使用 sidetable 來(lái)存儲(chǔ)引用計(jì)數(shù)
extra_rc 表示該對(duì)象的引用計(jì)數(shù)值

如何利用isa的位運(yùn)算得到類對(duì)象

通過(guò)上面的內(nèi)容我們可以知道shiftcls存儲(chǔ)著Class玷氏、Meta-Class對(duì)象的內(nèi)存地址信息堵未。


image.png
image.png

首先我們右移3位腋舌,跳過(guò) nonpointer盏触、has_assoc、has_cxx_dtor块饺,來(lái)到shiftcls頭部赞辩,然后左移31位再右移28位,這個(gè)時(shí)候我們來(lái)到了shiftcls尾部授艰,這個(gè)中間部分的就是我們的shiftcls的內(nèi)容辨嗽。用p/x LGPerson.class輸出打印驗(yàn)證,地址對(duì)應(yīng)上淮腾,這里就是存儲(chǔ)的我們的類對(duì)象Class糟需。

而蘋果給的方案就是:isa地址 & ISA_MASK = 類對(duì)象地址

image.png

注意驗(yàn)證的時(shí)候屉佳,要看清楚機(jī)型對(duì)應(yīng)的架構(gòu)。

總結(jié)

  1. OC對(duì)象??存儲(chǔ)了?個(gè)isa指針 + 成員變量的值影響了對(duì)象內(nèi)存洲押。
  2. 給對(duì)象添加實(shí)例方法和類方法對(duì)它的內(nèi)存并沒(méi)有任何影響,因?yàn)閷?shí)例方法存放在類對(duì)象中武花。而類方法存放在meta-class對(duì)象中。
  3. nonPointerIsa是isa的優(yōu)化杈帐。保存了引用計(jì)數(shù)等和對(duì)象一些息息相關(guān)的東西体箕。不同的架構(gòu)對(duì)應(yīng)的shiftcls占用的位數(shù)不一樣。
  4. 結(jié)構(gòu)體(struct)中所有變量是“共存”的挑童,?聯(lián)合體(union)中是各變量是“互斥”的累铅,只能存在?個(gè)。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末站叼,一起剝皮案震驚了整個(gè)濱河市娃兽,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌尽楔,老刑警劉巖换薄,帶你破解...
    沈念sama閱讀 222,183評(píng)論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異翔试,居然都是意外死亡轻要,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,850評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門垦缅,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)冲泥,“玉大人,你說(shuō)我怎么就攤上這事壁涎》不校” “怎么了?”我有些...
    開(kāi)封第一講書人閱讀 168,766評(píng)論 0 361
  • 文/不壞的土叔 我叫張陵怔球,是天一觀的道長(zhǎng)嚼酝。 經(jīng)常有香客問(wèn)我,道長(zhǎng)竟坛,這世上最難降的妖魔是什么闽巩? 我笑而不...
    開(kāi)封第一講書人閱讀 59,854評(píng)論 1 299
  • 正文 為了忘掉前任,我火速辦了婚禮担汤,結(jié)果婚禮上涎跨,老公的妹妹穿的比我還像新娘。我一直安慰自己崭歧,他們只是感情好隅很,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,871評(píng)論 6 398
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著率碾,像睡著了一般叔营。 火紅的嫁衣襯著肌膚如雪屋彪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書人閱讀 52,457評(píng)論 1 311
  • 那天绒尊,我揣著相機(jī)與錄音撼班,去河邊找鬼。 笑死垒酬,一個(gè)胖子當(dāng)著我的面吹牛砰嘁,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播勘究,決...
    沈念sama閱讀 40,999評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼矮湘,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了口糕?” 一聲冷哼從身側(cè)響起缅阳,我...
    開(kāi)封第一講書人閱讀 39,914評(píng)論 0 277
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎景描,沒(méi)想到半個(gè)月后十办,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,465評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡超棺,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,543評(píng)論 3 342
  • 正文 我和宋清朗相戀三年向族,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片棠绘。...
    茶點(diǎn)故事閱讀 40,675評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡件相,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出氧苍,到底是詐尸還是另有隱情夜矗,我是刑警寧澤,帶...
    沈念sama閱讀 36,354評(píng)論 5 351
  • 正文 年R本政府宣布让虐,位于F島的核電站紊撕,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏赡突。R本人自食惡果不足惜对扶,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,029評(píng)論 3 335
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望麸俘。 院中可真熱鬧辩稽,春花似錦惧笛、人聲如沸从媚。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 32,514評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)拜效。三九已至喷众,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間紧憾,已是汗流浹背到千。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 33,616評(píng)論 1 274
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留赴穗,地道東北人憔四。 一個(gè)月前我還...
    沈念sama閱讀 49,091評(píng)論 3 378
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像般眉,于是被迫代替她去往敵國(guó)和親了赵。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,685評(píng)論 2 360

推薦閱讀更多精彩內(nèi)容

  • 今天我們來(lái)對(duì)OC對(duì)象的原理進(jìn)行最后一篇文章的分析甸赃,在這里你講了解到一下內(nèi)容: 1柿汛、對(duì)象的底層本質(zhì)2、聯(lián)合體位域3埠对、...
    Jax_YD閱讀 485評(píng)論 0 1
  • 前言 我們都知道OC是一門面向?qū)ο蟮拈_(kāi)發(fā)語(yǔ)言络断,那么在開(kāi)發(fā)中免不了經(jīng)常會(huì)提起的一個(gè)詞,就是對(duì)象项玛。我們也經(jīng)常會(huì)說(shuō)萬(wàn)物皆...
    JasonL閱讀 456評(píng)論 0 1
  • 一貌笨、涉及知識(shí)點(diǎn) 1.共用體(聯(lián)合體) 定義 在進(jìn)行某些算法的C語(yǔ)言編程的時(shí)候,需要使幾種不同類型的變量存放到同一段...
    wendyJS閱讀 381評(píng)論 0 1
  • 第三節(jié)課 OC對(duì)象原理(下) 全篇開(kāi)始之前我們想一個(gè)問(wèn)題襟沮,研究了這么久對(duì)象躁绸,究竟什么是對(duì)象呢?臣嚣? 對(duì)象本質(zhì)以及拓展...
    不說(shuō)ryo閱讀 294評(píng)論 0 1
  • 前言 iOS底層探索之對(duì)象原理(一)中了解到通過(guò)calloc我們對(duì)象有了內(nèi)存地址净刮,通過(guò)initInstanceIs...
    litongde閱讀 232評(píng)論 0 2