手動目錄
- 內存分布及存儲
靜態(tài)變量安全- taggedPointer
特點
taggedPointer 演變
taggedPointer 存儲方式
引用計數處理方式
alloc 出來的對象引用計數
dealloc 干了什么
內存分布及存儲
為什么堆區(qū)比棧區(qū)的訪問速度慢挠羔?
棧區(qū)是寄存器直接讀取腮恩。
堆區(qū)的訪問,是寄存器先讀取棧區(qū)的指針地址偏窝,然后通過這個地址去堆區(qū)找到相應的數據。
棧區(qū)內存地址:一般0x7開頭
堆區(qū)內存地址:一般0x6開頭
數據段武学、BSS段地址:一般0x1開頭
// 全局變量祭往、全局靜態(tài)變量 初始化的在常量區(qū)(.data) ,未初始化的在靜態(tài)區(qū)(.bss)
int clA; //靜態(tài)區(qū)
int clB = 10; //常量區(qū)
static int bssA; //靜態(tài)區(qū)
static NSString *bssStr1; //靜態(tài)區(qū)
static int bssB = 10; //常量區(qū)
static NSString *bssStr2 = @"name"; //常量區(qū)
- (void)testStack{
int a = 10; // 棧區(qū)
int b = 20; // 棧區(qū)
NSObject *object = [NSObject new]; // *obj 棧區(qū) , obj 堆區(qū)
NSString *str = @"aaa"; // *str 棧區(qū) , str 常量區(qū)(.data)
NSString *str1; // *str1 棧區(qū) , str1 0x0
}
靜態(tài)變量安全
如下代碼
// Person 類 定義一個靜態(tài)變量 personHei
static int personHeig = 180;
@interface Person : NSObject
- (void)growUp;
@end
@implementation Person
- (void)growUp {
personAge = 30;
NSLog(@"person age = %d %p",personAge,&personAge); // person age = 30 0x10be0fe10
}
@end
// 在另外一個類中區(qū)訪問并修改
- (void)task8 {
NSLog(@"person age = %d %p",personAge,&personAge); // person age = 18 0x10be0fc18
personAge = 30;
NSLog(@"person age = %d %p",personAge,&personAge); // person age = 30 0x10be0fc18
[[Person new] growUp];
personAge ++;
NSLog(@"person age = %d %p",personAge,&personAge); // person age = 31 0x10be0fc18
}
//最后打印結果:
person age = 18 0x10be0fc18
person age = 30 0x10be0fc18
person age = 30 0x10be0fe10
person age = 31 0x10be0fc18
對于靜態(tài)全局變量,對比內存地址和值火窒,我們發(fā)現:
在同一個文件中硼补,訪問的全局靜態(tài)變量 地址相同,而且可以正常的修改
熏矿,
而對于不同文件中的全局靜態(tài)變量 地址是不同的括勺,而且修改是相互不影響
。
personAge 在Person類中曲掰,地址和在另外一個類中的地址不同疾捍,而且修改之后相互不影響。
taggedPointer
taggedPointer是干嘛用的栏妖? 用于優(yōu)化內存的
乱豆。
特點
- Tagged Pointer專門用來存儲小的對象,例如NSNumber和NSDate
- Tagged Pointer指針的值不再是地址了吊趾,而是真正的值宛裕。所以瑟啃,實際上它不再是一個對象了,它只是一個披著對象皮的普通變量而已揩尸。所以蛹屿,
它的內存并不存儲在堆中,也不需要 malloc 和 free
岩榆。不需要引用計數處理错负,與系統(tǒng)自動回收 - 在內存讀取上有著 3 倍的效率,創(chuàng)建時比以前快 106 倍勇边。
在iPhone5s之前犹撒,蘋果系統(tǒng)是32位,而5s出來之后粒褒,是64位识颊,
在32位系統(tǒng)中,小對象(比如NSNumber奕坟、NSData)用8位去存儲值都能滿足大部分情況祥款,如果還用這8位去存儲一個地址指針,就太浪費內存了月杉。蘋果為了優(yōu)化這個問題刃跛,而引入taggedPointer
的方式。
簡單來講可以理解為把指針指向的內容直接放在了指針變量的內存地址中沙合,因為在 64 位環(huán)境下指針變量的大小達到了 8 位足以容納一些長度較小的內容。于是使用了標簽指針這種方式來優(yōu)化數據的存儲方式跌帐。
taggedPointer 演變
-
早期
taggedPointer 的地址 直接存儲值 比如 Number = @(1)的對象首懈,其存儲形式為:
__NSCFNumber, 0xb000000000000013
參考文章- (void)task { NSNumber *number1 = @1; NSNumber *bigNumber = @(0x7fffffffffffff + 1); // 14位 NSLog(@"number1 pointer is %p", number1); NSLog(@"bigNumber pointer is %p", bigNumber); } // 打印結果 number1 pointer is 0xb000000000000012 bigNumber pointer is 0x10921ecc0
-
優(yōu)化
在10.14之后,又進行了一次優(yōu)化:
在 Objc源碼中有這樣一段:
在程序啟動的源碼中:objc_init() -> map_images() -> read_image()
initializeTaggedPointerObfuscator(void) { if (sdkIsOlderThan(10_14, 12_0, 12_0, 5_0, 3_0) || // Set the obfuscator to zero for apps linked against older SDKs, // in case they're relying on the tagged pointer representation. DisableTaggedPointerObfuscation) { objc_debug_taggedpointer_obfuscator = 0; } else { // Pull random data into the variable, then shift away all non-payload bits. arc4random_buf(&objc_debug_taggedpointer_obfuscator, sizeof(objc_debug_taggedpointer_obfuscator)); objc_debug_taggedpointer_obfuscator &= ~_OBJC_TAG_MASK; } } // taggedPointer 對象進行編碼解碼的過程中谨敛,進行了異或運算 static inline void * _Nonnull _objc_encodeTaggedPointer(uintptr_t ptr) { return (void *)(objc_debug_taggedpointer_obfuscator ^ ptr); } static inline uintptr_t _objc_decodeTaggedPointer(const void * _Nullable ptr) { return (uintptr_t)ptr ^ objc_debug_taggedpointer_obfuscator; }
優(yōu)化之后要打印出實際指針信息 究履,采用以下方式:
#import <objc/runtime.h> extern uintptr_t objc_debug_taggedpointer_obfuscator; uintptr_t _objc_decodeTaggedPointer_(id ptr) { return (uintptr_t)ptr ^ objc_debug_taggedpointer_obfuscator; } NSNumber *number1 = @(1); NSLog(@"%@-%p---%@ - 0x%lx",object_getClass(number1),number1,number1,_objc_decodeTaggedPointer_(number1)); // 打印結果 __NSCFNumber-0xcdfb8d4002f69cf0---1 - 0xb000000000000012
taggedPointer 存儲方式
最后一位表示的含義:
typedef uint16_t objc_tag_index_t;
enum
#endif
{
// 60-bit payloads
OBJC_TAG_NSAtom = 0,
OBJC_TAG_1 = 1,
OBJC_TAG_NSString = 2,
OBJC_TAG_NSNumber = 3,
OBJC_TAG_NSIndexPath = 4,
OBJC_TAG_NSManagedObjectID = 5,
OBJC_TAG_NSDate = 6,
// 60-bit reserved
OBJC_TAG_RESERVED_7 = 7,
// 52-bit payloads
OBJC_TAG_Photos_1 = 8,
OBJC_TAG_Photos_2 = 9,
OBJC_TAG_Photos_3 = 10,
OBJC_TAG_Photos_4 = 11,
OBJC_TAG_XPC_1 = 12,
OBJC_TAG_XPC_2 = 13,
OBJC_TAG_XPC_3 = 14,
OBJC_TAG_XPC_4 = 15,
OBJC_TAG_NSColor = 16,
OBJC_TAG_UIColor = 17,
OBJC_TAG_CGColor = 18,
OBJC_TAG_NSIndexSet = 19,
OBJC_TAG_First60BitPayload = 0,
OBJC_TAG_Last60BitPayload = 6,
OBJC_TAG_First52BitPayload = 8,
OBJC_TAG_Last52BitPayload = 263,
OBJC_TAG_RESERVED_264 = 264
};
引用計數處理方式
如何維護引用計數呢?
在之前的的 isa結構探究 中 提到 isa結構中脸狸,有引用計數的記錄最仑。
extra_rc :19
當表示該對象的引用計數值,實際上是引用計數值減1炊甲,例如泥彤,如果對象的引用計數為10,那么extra_rc為9.如果引用計數大于10卿啡,則需要使用上面提到的has_sidetable_rc吟吝。
那么在對象進行retain的時候, 那么具體是進行了什么操作呢颈娜?
id
objc_retain(id obj)
{
if (!obj) return obj;
if (obj->isTaggedPointer()) return obj; // ?? 如果是 taggedPointer ,就不進行retain
return obj->retain();
}
ALWAYS_INLINE id
objc_object::rootRetain(bool tryRetain, bool handleOverflow)
{
if (isTaggedPointer()) return (id)this;
bool sideTableLocked = false;
bool transcribeToSideTable = false;
isa_t oldisa;
isa_t newisa;
do {
transcribeToSideTable = false;
oldisa = LoadExclusive(&isa.bits);
newisa = oldisa;
if (slowpath(!newisa.nonpointer)) { // 如果不是 nonpoint isa 直接存散列表
ClearExclusive(&isa.bits);
if (rawISA()->isMetaClass()) return (id)this;
if (!tryRetain && sideTableLocked) sidetable_unlock();
if (tryRetain) return sidetable_tryRetain() ? (id)this : nil;
else return sidetable_retain();
}
// don't check newisa.fast_rr; we already called any RR overrides
if (slowpath(tryRetain && newisa.deallocating)) {
ClearExclusive(&isa.bits);
if (!tryRetain && sideTableLocked) sidetable_unlock();
return nil;
}
uintptr_t carry;
newisa.bits = addc(newisa.bits, RC_ONE, 0, &carry); // extra_rc++ // 如果是nonpoint isa 就進行isa 的extra_rc++ 操作
if (slowpath(carry)) { // 如果isa的 extra_rc 超過 容量剑逃,
// newisa.extra_rc++ overflowed
if (!handleOverflow) {
ClearExclusive(&isa.bits);
return rootRetain_overflow(tryRetain);
}
// Leave half of the retain counts inline and
// prepare to copy the other half to the side table.
if (!tryRetain && !sideTableLocked) sidetable_lock();
sideTableLocked = true;
transcribeToSideTable = true;
newisa.extra_rc = RC_HALF; // isa 中的 extra_rc 保存一半
newisa.has_sidetable_rc = true;
}
} while (slowpath(!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)));
if (slowpath(transcribeToSideTable)) {
// Copy the other half of the retain counts to the side table.
sidetable_addExtraRC_nolock(RC_HALF); // 散列表中的引用計數表 存一半
}
if (slowpath(!tryRetain && sideTableLocked)) sidetable_unlock();
return (id)this;
}
引用計數的處理
retain
- 如果不是 nonpoint isa 直接加入散列表
- 如果是 nonpoint isa 在isa的 extra_rc 進行++操作
如果超過容量浙宜,isa 中的 extra_rc 保存一半 散列表中的引用計數表 存一半引用計數的處理
release
- 如果不是 nonpoint isa 直接對散列表進行操作
- 如果是 nonpoint isa 在isa的 extra_rc 進行-- 操作
如果減到最后沒了,看有沒有相應的散列表的引用計數表蛹磺,如果有粟瞬,把引用計數表的計數賦值給extra_rc。
補充:
散列表有多張(據說最多64張)萤捆,每張散列表維護 三張表裙品,多表結構的目的是為了:安全、高效
鳖轰。對表進行操作清酥,需要加鎖/解鎖,同時可能由多個任務需要加鎖/解鎖蕴侣。所以用多表可以提高查詢速度焰轻、提高執(zhí)行速度。
struct SideTable {
spinlock_t slock; // 自旋鎖
RefcountMap refcnts; //引用計數表
weak_table_t weak_table; // 弱引用表1
}
alloc 出來的對象引用計數
這也是一個面試題:alloc出來的對象昆雀,其引用計數是多少辱志?
答案是0。
在之前的iOS底層-alloc與init 中詳細的說了alloc的過程狞膘,在此過程中揩懒,我們并沒有看到任何與引用計數有關的內容,也就是說挽封,并沒有操作引用計數已球,所以為0 。
但是 為什么通過打印的方式得到的是1 呢辅愿?
printf("Retain Count = %ld\n",CFGetRetainCount((__bridge CFTypeRef)(obj)));
通過源碼來看:
inline uintptr_t
objc_object::rootRetainCount()
{
if (isTaggedPointer()) return (uintptr_t)this;
sidetable_lock();
isa_t bits = LoadExclusive(&isa.bits);
ClearExclusive(&isa.bits);
if (bits.nonpointer) {
uintptr_t rc = 1 + bits.extra_rc;
if (bits.has_sidetable_rc) {
rc += sidetable_getExtraRC_nolock();
}
sidetable_unlock();
return rc;
}
sidetable_unlock();
return sidetable_retainCount();
}
打印出來的引用計數值的又來: 1 + isa.extra_rc + sidetable_rc(散列表的引用計數)
既然打印結果為1智亮,那么 其 isa.extra_rc 必然為0, 所以其真正的引用計數為0点待。
為什么默認要給個1呢阔蛉,因為 它要被autorelease(自動釋放池)所持有。
dealloc 干了什么
看源碼:
inline void
objc_object::rootDealloc()
{
if (isTaggedPointer()) return;
object_dispose((id)this);
}
id
object_dispose(id obj)
{
if (!obj) return nil;
objc_destructInstance(obj);
free(obj);
return nil;
}
void *objc_destructInstance(id obj)
{
if (obj) {
// Read all of the flags at once for performance.
bool cxx = obj->hasCxxDtor();
bool assoc = obj->hasAssociatedObjects();
// This order is important.
if (cxx) object_cxxDestruct(obj);
if (assoc) _object_remove_assocations(obj); // 清空關聯對象
obj->clearDeallocating();
}
return obj;
}
inline void
objc_object::clearDeallocating()
{
if (slowpath(!isa.nonpointer)) {
// Slow path for raw pointer isa.
sidetable_clearDeallocating(); // 非 nonpointer 散列表清空
}
else if (slowpath(isa.weakly_referenced || isa.has_sidetable_rc)) {
// Slow path for non-pointer isa with weak refs and/or side table data.
clearDeallocating_slow();
}
assert(!sidetable_present());
}
objc_object::clearDeallocating_slow()
{
ASSERT(isa.nonpointer && (isa.weakly_referenced || isa.has_sidetable_rc));
SideTable& table = SideTables()[this];
table.lock();
if (isa.weakly_referenced) {
weak_clear_no_lock(&table.weak_table, (id)this); // 清空弱引用表中的弱引用對象
}
if (isa.has_sidetable_rc) {
table.refcnts.erase(this); // 清空散列表中的引用計數表的引用計數
}
table.unlock();
}
dealloc 干了什么
- 清空關聯對象
- 清空 弱引用對象
- 清空 散列表中的引用計數表