上一篇 介紹了alloc的調(diào)用順序和init及new的底層邏輯。這一篇將帶大家繼續(xù)了解對(duì)象的內(nèi)存分布南蓬。
影響對(duì)象內(nèi)存的因素
對(duì)象??存儲(chǔ)了?個(gè)isa指針 + 成員變量的值藻治,蠢甲,所以影響對(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é)涂臣。
class_getInstanceSize()執(zhí)行流程圖:
接下來(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ì)象的成員變量的具體的值。
蘋果會(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;
}
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)存地址信息堵未。
首先我們右移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ì)象地址
注意驗(yàn)證的時(shí)候屉佳,要看清楚機(jī)型對(duì)應(yīng)的架構(gòu)。
總結(jié)
- OC對(duì)象??存儲(chǔ)了?個(gè)isa指針 + 成員變量的值影響了對(duì)象內(nèi)存洲押。
- 給對(duì)象添加實(shí)例方法和類方法對(duì)它的內(nèi)存并沒(méi)有任何影響,因?yàn)閷?shí)例方法存放在類對(duì)象中武花。而類方法存放在meta-class對(duì)象中。
- nonPointerIsa是isa的優(yōu)化杈帐。保存了引用計(jì)數(shù)等和對(duì)象一些息息相關(guān)的東西体箕。不同的架構(gòu)對(duì)應(yīng)的shiftcls占用的位數(shù)不一樣。
- 結(jié)構(gòu)體(struct)中所有變量是“共存”的挑童,?聯(lián)合體(union)中是各變量是“互斥”的累铅,只能存在?個(gè)。