一、block記憶體管理 1.block記憶體類型 block記憶體分為三種類型: _NSConcreteGlobalBlock(全局) _NSConcreteStackBlock(棧) _NSConcreteMallocBlock(堆) 2.三種類型的記憶體的創建時機 1)對於_NSConcreteSta ...
一、block記憶體管理
1.block記憶體類型
block記憶體分為三種類型:
- _NSConcreteGlobalBlock(全局)
- _NSConcreteStackBlock(棧)
- _NSConcreteMallocBlock(堆)
2.三種類型的記憶體的創建時機
1)對於_NSConcreteStackBlock
和_NSConcreteGlobalBlock
類型
_NSConcreteStackBlock
和_NSConcreteGlobalBlock
這兩種類型的block,我們可以手動創建,如下所示:
void (^globalBlock)() = ^{
};
int main(int argc, const char * argv[]) {
@autoreleasepool {
void (^stackBlock1)() = ^{
};
}
return 0;
}
那麼我們怎麼確定這兩個block,就是我們所說的兩種類型的block呢,我們可以使用clang -rewrite-objc xxx.m
(報錯可以使用詳細命令: clang -x objective-c -rewrite-objc -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk xxxxx.m
)編譯轉換成C++實現,就可以看到轉換完的結果,如下所示:
// globalBlock
struct __globalBlock_block_impl_0 {
struct __block_impl impl;
struct __globalBlock_block_desc_0* Desc;
__globalBlock_block_impl_0(void *fp, struct __globalBlock_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteGlobalBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
...
// stackBlock
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
...
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
void (*stackBlock)() = (void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA);
}
return 0;
}
可以看出可以看出globalBlock是_NSConcreteGlobalBlock類型,即在全局區域創建,block變數存儲在全局數據存儲區;stackBlock是_NSConcreteStackBlock類型,即在棧區創建。
2)對於_NSConcreteMallocBlock
類型
NSConcreteMallocBlock
類型的記憶體是通過_NSConcreteStackBlock
類型的block copy得到的,那麼哪些類型會對block進行copy呢?
- block作為返回值
// 如果是weak類型的block,依然不會自動進行copy
// <__NSStackBlock__: 0x7fff5fbff728>
__weak void (^weakBlock)() = ^{i;};
// ARC情況下輸出
// <__NSMallocBlock__
NSLog(@"%@", [self callBack:weakBlock]);
- (id)callBack:(void (^)(void))callBack
{
NSLog(@"%@", callBack);
return callBack;
}
//輸出結果
<__NSStackBlock__: 0x7ffee2559838>
<__NSMallocBlock__: 0x600003a99ce0>
- block作為屬性,使用copy修飾時(strong修飾符不會改變block記憶體類型)
@property (copy, nonatomic) id myCopyBlock;
@property (strong, nonatomic) id myStrongBlock;
// 如果是weak類型的block,依然不會自動進行copy
// <__NSStackBlock__: 0x7fff5fbff728>
__weak void (^weakBlock)() = ^{i;};
NSLog(@"%@", weakBlock);
//會進行copy操作
//<__NSMallocBlock__: 0x6000037e8db0>
self.myCopyBlock = weakBlock;
NSLog(@"%@", self.myCopyBlock);
// 會進行strong操作
// <__NSStackBlock__: 0x7fff5fbff728>
self.myStrongBlock = weakBlock;
NSLog(@"%@", self.myStrongBlock);
//列印結果
//<__NSStackBlock__: 0x7ffee8ed5838>
//<__NSMallocBlock__: 0x6000037e8db0>
//<__NSStackBlock__: 0x7ffee8ed5838>
- block為strong類型,且捕獲了外部變數時。
int i = 10;
void (^block)() = ^{i;};
// 因為block為strong類型,且捕獲了外部變數,所以賦值時,自動進行了copy
// <__NSMallocBlock__: 0x100206920>
NSLog(@"%@", block);
對於作為參數傳遞的block,其類型是什麼呢?
int i = 10;
void (^block)() = ^{i;};
__weak void (^weakBlock)() = ^{i;};
void (^stackBlock)() = ^{};
// ARC情況下
// 創建時,都會在棧中
// <__NSStackBlock__: 0x7fff5fbff730>
NSLog(@"%@", ^{i;});
// 因為block為strong類型,且捕獲了外部變數,所以賦值時,自動進行了copy
// <__NSMallocBlock__: 0x100206920>
NSLog(@"%@", block);
// 如果是weak類型的block,依然不會自動進行copy
// <__NSStackBlock__: 0x7fff5fbff728>
NSLog(@"%@", weakBlock);
// 如果block是strong類型,並且沒有捕獲外部變數,那麼就會轉換成__NSGlobalBlock__
// <__NSGlobalBlock__: 0x100001110>
NSLog(@"%@", stackBlock);
[self callBack:weakBlock];
[self callBack:block];
[self callBack:stackBlock];
- (id)callBack:(void (^)(void))callBack
{
NSLog(@"%@", callBack);
return callBack;
}
//結果
//<__NSStackBlock__: 0x7ffee2572838>
//<__NSMallocBlock__: 0x600002e881e0>
// <__NSGlobalBlock__: 0x10d68c0f8>
//<__NSStackBlock__: 0x7ffee2572838>
//<__NSMallocBlock__: 0x600002e881e0>
//<__NSGlobalBlock__: 0x10d68c0f8>
我們可以發現函數參數的block為什麼類型,block在函數中就是什麼類型。
二、autorelease記憶體管理
1、哪些對象是autorelease管理的?
1)enumerateObjectsUsingBlock中的對象
[NSArray array] enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
//自動緩存池
}
2)__autoreleasing 修飾的對象
id obj = [NSObject new];
id __autoreleasing o = obj;
3)array、dictiongnary、stringWithString等非init或者new方法生成的對象
int main(int argc, char * argv[]) {
NSMutableArray *array = [NSMutableArray array];
NSMutableArray *array1 = [NSMutableArray arrayWithCapacity:5];
NSMutableDictionary *dict = [NSMutableDictionary dictionary];
NSMutableString *str = [NSMutableString stringWithString:@"dsdsds"];
以上類型實驗結果:
int main(int argc, char * argv[]) {
id obj = [NSObject new];
id __autoreleasing o = obj;
id __autoreleasing o1 = obj;
NSMutableArray *array = [NSMutableArray arrayWithCapacity:5];
[array addObject:@"0"];
[array addObject:@"1"];
[array addObject:@"2"];
[array addObject:@"3"];
[array addObject:@"4"];
[array addObject:@"5"];
[array addObject:@"6"];
NSMutableArray *array1 = [NSMutableArray array];
[array1 addObject:@"11"];
[array1 addObject:@"12"];
[array1 addObject:@"13"];
[array1 addObject:@"14"];
[array1 addObject:@"15"];
[array1 addObject:@"16"];
[array1 enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
id __autoreleasing o = obj;
}];
NSMutableDictionary *dict = [NSMutableDictionary dictionary];
[dict setObject:@"1" forKey:@"1"];
NSMutableString *str = [NSMutableString stringWithString:@"dsdsds"];
// _objc_autoreleasePoolPrint()
}
//在armv7上、使用_objc_autoreleasePoolPrint()調試列印結果
(lldb) po _objc_autoreleasePoolPrint()
objc[96185]: ##############
objc[96185]: AUTORELEASE POOLS for thread 0x20d080
objc[96185]: 6 releases pending.
objc[96185]: [0x7e115000] ................ PAGE (hot) (cold)
objc[96185]: [0x7e115028] 0x7be71ca0 NSObject
objc[96185]: [0x7e11502c] 0x7be71ca0 NSObject
objc[96185]: [0x7e115030] 0x7c470560 __NSArrayM
objc[96185]: [0x7e115034] 0x7be723b0 __NSArrayM
objc[96185]: [0x7e115038] 0x7c170b80 __NSDictionaryM
objc[96185]: [0x7e11503c] 0x7be72540 __NSCFString
objc[96185]: ##############
0x0a5c2500
//在arm64的手機上、使用_objc_autoreleasePoolPrint()調試列印結果
(lldb) po _objc_autoreleasePoolPrint()
objc[96400]: ##############
objc[96400]: AUTORELEASE POOLS for thread 0x1151d75c0
objc[96400]: 5 releases pending.
objc[96400]: [0x7fae43000000] ................ PAGE (hot) (cold)
objc[96400]: [0x7fae43000038] 0x600003a6c840 __NSArrayI//系統創建對象
objc[96400]: [0x7fae43000040] 0x600000c358b0 __NSSetI//系統創建對象
objc[96400]: [0x7fae43000048] 0x600002d380d0 NSObject
objc[96400]: [0x7fae43000050] 0x600002d380d0 NSObject
objc[96400]: [0x7fae43000058] 0x6000021649f0 __NSArrayM
objc[96400]: ##############
0xe0675b6edaa1003f
(lldb) po 0x6000021649f0
<__NSArrayM 0x600001435d70>(
0,
1,
2,
3,
4,
5,
6
)
註意:這裡面的實驗結果不一樣,在arm64上、array、dictiongnary、stringWithString等方法生成的對象
,在自動緩存池中只能看見第一個對象,而armv7的機型上,可以看見所有的,不知這裡是什麼原因,有知道的歡迎告訴我
兩個常用的調試命令
//列印自動緩存池對象
_objc_autoreleasePoolPrint()
//列印引用計數
_objc_rootRetainCount(obj)
作為一個開發者,有一個學習的氛圍跟一個交流圈子特別重要,這是一個我的iOS交流群:519832104 不管你是小白還是大牛歡迎入駐,分享經驗,討論技術,大家一起交流學習成長!
另附上一份各好友收集的大廠面試題,需要iOS開發學習資料、面試真題,可以添加iOS開發進階交流群,進群可自行下載!
2、autoreleasePool什麼時候創建的,裡面的對象又是什麼時候釋放的?
1)系統通過runloop創建的autoreleasePool
runloop 可以說是iOS 系統的靈魂。記憶體管理/UI 刷新/觸摸事件這些功能都需要 runloop 去管理和實現。runloop是通過線程創建的,和線程保持一對一的關係,其關係是保存在一個全局的 Dictionary 里。線程剛創建時並沒有 RunLoop,如果你不主動獲取,那它一直都不會有。RunLoop 的創建是發生在第一次獲取時,RunLoop 的銷毀是發生線上程結束時。你只能在一個線程的內部獲取其 RunLoop(主線程除外)。
runloop和autoreleasePool又是什麼關係呢?對象又是什麼時候釋放的?
App啟動後,蘋果在主線程 RunLoop 里註冊了兩個 Observer,其回調都是 _wrapRunLoopWithAutoreleasePoolHandler()。
第一個 Observer 監視的事件是 Entry(即將進入Loop),其回調內會調用 _objc_autoreleasePoolPush() 創建自動釋放池。其 order 是-2147483647,優先順序最高,保證創建釋放池發生在其他所有回調之前。
第二個 Observer 監視了兩個事件: BeforeWaiting(準備進入休眠) 時調用_objc_autoreleasePoolPop() 和 _objc_autoreleasePoolPush() 釋放舊的池並創建新池;Exit(即將退出Loop) 時調用 _objc_autoreleasePoolPop() 來釋放自動釋放池。
這個 Observer 的 order 是 2147483647,優先順序最低,保證其釋放池子發生在其他所有回調之後。
在主線程執行的代碼,通常是寫在諸如事件回調、Timer回調內的。這些回調會被 RunLoop 創建好的 AutoreleasePool 環繞著,所以不會出現記憶體泄漏,開發者也不必顯示創建 Pool 了。
驗證結果:
int main(int argc, char * argv[]) {
id obj = [NSObject new];
id __autoreleasing o = obj;
id __autoreleasing o1 = obj;
NSMutableArray *array = [NSMutableArray arrayWithCapacity:5];
[array addObject:@"0"];
[array addObject:@"1"];
[array addObject:@"2"];
[array addObject:@"3"];
[array addObject:@"4"];
[array addObject:@"5"];
[array addObject:@"6"];
// _objc_autoreleasePoolPrint()
}
//_objc_autoreleasePoolPrint調試列印結果
(lldb) po _objc_autoreleasePoolPrint()
objc[99121]: ##############
objc[99121]: AUTORELEASE POOLS for thread 0x107b0d5c0
objc[99121]: 5 releases pending.
objc[99121]: [0x7f93b2002000] ................ PAGE (hot) (cold)
objc[99121]: [0x7f93b2002038] 0x6000000d66c0 __NSArrayI
objc[99121]: [0x7f93b2002040] 0x6000036b9680 __NSSetI
objc[99121]: [0x7f93b2002048] 0x600001780160 NSObject
objc[99121]: [0x7f93b2002050] 0x600001780160 NSObject
objc[99121]: [0x7f93b2002058] 0x600001bcd230 __NSArrayM
objc[99121]: ##############
0x67c4279ea7c20079
(lldb) po 0x600001bcd230
<__NSArrayM 0x600001bcd230>(
0,
1,
2,
3,
4,
5,
6
)
(lldb) po [NSThread currentThread]
<NSThread: 0x6000000953c0>{number = 1, name = main}
2)手動autoreleasePool
我們可以通過@autoreleasepool {}
方式手動創建autoreleasepool對象,那麼這個對象什麼時候釋放呢?答案是除了autoreleasepool的大括弧就釋放了,我們可以看下下麵的實驗結果
int main(int argc, char * argv[]) {
//1\. _objc_autoreleasePoolPrint()
@autoreleasepool {
id obj = [NSObject new];
id __autoreleasing o = obj;
id __autoreleasing o1 = obj;
//2\. _objc_autoreleasePoolPrint()
}
//3\. _objc_autoreleasePoolPrint()
}
//1\. _objc_autoreleasePoolPrint()
(lldb) po _objc_autoreleasePoolPrint()
objc[1555]: ##############
objc[1555]: AUTORELEASE POOLS for thread 0x11331a5c0
objc[1555]: 2 releases pending.
0x2196ee78f1e100fd
objc[1555]: [0x7fc2a9802000] ................ PAGE (hot) (cold)
objc[1555]: [0x7fc2a9802038] 0x600002dbb600 __NSArrayI
objc[1555]: [0x7fc2a9802040] 0x600001bd8a50 __NSSetI
objc[1555]: ##############
//2\. _objc_autoreleasePoolPrint()
(lldb) po _objc_autoreleasePoolPrint()
objc[1555]: ##############
objc[1555]: AUTORELEASE POOLS for thread 0x11331a5c0
0x2196ee78f1e100fd
objc[1555]: 5 releases pending.
objc[1555]: [0x7fc2a9802000] ................ PAGE (hot) (cold)
objc[1555]: [0x7fc2a9802038] 0x600002dbb600 __NSArrayI
objc[1555]: [0x7fc2a9802040] 0x600001bd8a50 __NSSetI
objc[1555]: [0x7fc2a9802048] ################ POOL 0x7fc2a9802048
objc[1555]: [0x7fc2a9802050] 0x600003afc030 NSObject
objc[1555]: [0x7fc2a9802058] 0x600003afc030 NSObject
objc[1555]: ##############
//3\. _objc_autoreleasePoolPrint()
(lldb) po _objc_autoreleasePoolPrint()
objc[1555]: ##############
objc[1555]: AUTORELEASE POOLS for thread 0x11331a5c0
0x2196ee78f1e100fd
objc[1555]: 2 releases pending.
objc[1555]: [0x7fc2a9802000] ................ PAGE (hot) (cold)
objc[1555]: [0x7fc2a9802038] 0x600002dbb600 __NSArrayI
objc[1555]: [0x7fc2a9802040] 0x600001bd8a50 __NSSetI
objc[1555]: ##############
(lldb)
從上面1、2、3的結果可以看出,當對象出了autoreleasepool的大括弧就釋放了。
3、子線程的autoreleasepool對象的管理?
線程剛創建時並沒有 RunLoop,如果你不主動獲取,那它一直都不會有。所以在我們創建子線程的時候,如果沒有獲取runloop,那麼也就沒用通過runloop來創建autoreleasepool,那麼我們的autorelease對象是怎麼管理的,會不會存在記憶體泄漏呢?答案是否定的,當子線程有autoreleasepool的時候,autorelease對象通過其來管理,如果沒有autoreleasepool,會通過調用 autoreleaseNoPage 方法,將對象添加到 AutoreleasePoolPage 的棧中,也就是說你不進行手動的記憶體管理,也不會記憶體泄漏啦!這部分我們可以看下runtime中NSObject.mm的部分,有相關代碼。
static inline id *autoreleaseFast(id obj)
{
AutoreleasePoolPage *page = hotPage();
if (page && !page->full()) {
return page->add(obj);
} else if (page) {
return autoreleaseFullPage(obj, page);
} else {
//調用 autoreleaseNoPage 方法管理autorelease對象。
return autoreleaseNoPage(obj);
}
}
三、weak對象記憶體管理
1.釋放時機
在dealloc的時候,會將weak屬性的值設置為nil
2.如何實現
Runtime維護了一個weak表,用於存儲指向某個對象的所有weak指針,對於 weak 對象會放入一個 hash 表中,Key是所指對象的地址,Value是weak指針的地址(這個地址的值是所指對象的地址)數組。 當此對象的引用計數為0的時候會 dealloc,假如 weak 指向的對象記憶體地址是a,那麼就會以a為鍵, 在這個 weak 表中搜索,找到所有以a為鍵的 weak 對象,從而設置為 nil。
註:由於可能多個weak指針指向同一個對象,所以value為一個數組
weak 的實現原理可以概括以下三步:
1)初始化時:runtime會調用objc_initWeak函數,初始化一個新的weak指針指向對象的地址。
我們以下麵這行代碼為例:
代碼清單1:示例代碼
{
id __weak obj1 = obj;
}
當我們初始化一個weak變數時,runtime會調用objc_initWeak函數。這個函數在Clang中的聲明如下:
id objc_initWeak(id *object, id value);
其具體實現如下:
id objc_initWeak(id *object, id value)
{
*object = 0;
return objc_storeWeak(object, value);
}
示例代碼輪換成編譯器的模擬代碼如下:
id obj1;
objc_initWeak(&obj1, obj);
因此,這裡所做的事是先將obj1初始化為0(nil),然後將obj1的地址及obj作為參數傳遞給objc_storeWeak函數。
objc_initWeak函數有一個前提條件:就是object必須是一個沒有被註冊為__weak對象的有效指針。而value則可以是null,或者指向一個有效的對象。
2)添加引用時:objc_initWeak函數會調用 objc_storeWeak() 函數。
objc_storeWeak() 的作用是更新指針指向,創建對應的弱引用表。
3)釋放時,調用clearDeallocating函數。
clearDeallocating函數首先根據對象地址獲取所有weak指針地址的數組,然後遍歷這個數組把其中的數據設為nil,最後把這個entry從weak表中刪除,最後清理對象的記錄。
四、NSString記憶體管理
1.NSString記憶體的類型
NSString記憶體分為兩種類型:
- __NSCFConstantString(常量區)
- __NSCFString(堆區)、NSTaggedPointerString(堆區)
2.兩種記憶體類型的創建時機。
生成一個NSString類型的字元串有三種方法:
- 方法1.直接賦值:
NSString *str1 = @"my string";
- 方法2.類函數初始化生成:
NSString *str2 = [NSString stringWithString:@"my string"];
- 方法3.實例方法初始化生成:
NSString *str3 = [[NSString alloc] initWithString:@"my string"];
NSString *str4 = [[NSString alloc]initWithFormat:@"my string"];
1)對於__NSCFConstantString
這種類型的字元串是常量字元串。該類型的字元串以字面量的方式創建,保存在字元串常量區,是在編譯時創建的。
NSString *a = @"str";
NSString *b = [[NSString alloc]init];
NSString *c = [[NSString alloc]initWithString:@"str"];
NSString *d = [NSString stringWithString:@"str"];
NSLog(@"%@ : class = %@",a,NSStringFromClass([a class]));
NSLog(@"%@ : class = %@",b,NSStringFromClass([b class]));
NSLog(@"%@ : class = %@",c,NSStringFromClass([c class]));
NSLog(@"%@ : class = %@",d,NSStringFromClass([d class]));
//列印結果
2019-06-23 19:23:13.240611+0800 BlockDemo[47229:789011] str : class = __NSCFConstantString
2019-06-23 19:23:13.240764+0800 BlockDemo[47229:789011] : class = __NSCFConstantString
2019-06-23 19:23:13.240870+0800 BlockDemo[47229:789011] str : class = __NSCFConstantString
2019-06-23 19:23:13.240957+0800 BlockDemo[47229:789011] str : class = __NSCFConstantString
2)對於__NSCFString
和NSTaggedPointerString
- __NSCFString 表示對象類型的字元串,在運行時創建,保存在堆區,初始引用計數為1,其記憶體管理方式就是對象的記憶體管理方式。
- NSTaggedPointerString是對__NSCFString類型的一種優化,在運行創建字元串時,會對字元串內容及長度作判斷,若內容由ASCII字元構成且長度較小(具體要多小暫時不太清楚),這時候創建的字元串類型就是 NSTaggedPointerString
對於不可以變NSString的測試結果:
NSString *e = [[NSString alloc]initWithFormat:@"str"];
NSString *f = [NSString stringWithFormat:@"str"];
NSString *g = [NSString stringWithFormat:@"123456789"];
NSString *h = [NSString stringWithFormat:@"1234567890"];
NSLog(@"%@ : class = %@",e,NSStringFromClass([e class]));
NSLog(@"%@ : class = %@",f,NSStringFromClass([f class]));
NSLog(@"%@ : class = %@",g,NSStringFromClass([g class]));
NSLog(@"%@ : class = %@",h,NSStringFromClass([h class]));
//列印結果
2019-06-23 19:27:19.115212+0800 BlockDemo[48129:794364] str : class = NSTaggedPointerString
2019-06-23 19:27:19.115286+0800 BlockDemo[48129:794364] str : class = NSTaggedPointerString
2019-06-23 19:27:19.115388+0800 BlockDemo[48129:794364] 123456789 : class = NSTaggedPointerString
2019-06-23 19:27:19.115476+0800 BlockDemo[48129:794364] 1234567890 : class = __NSCFString
對於可變的NSMutableString
NSMutableString *ms1 = [[NSMutableString alloc]init];
NSMutableString *ms2 = [[NSMutableString alloc]initWithString:@"str"];
NSMutableString *ms3 = [[NSMutableString alloc]initWithFormat:@"str"];
NSMutableString *ms4 = [NSMutableString stringWithFormat:@"str"];
NSMutableString *ms5 = [NSMutableString stringWithFormat:@"123456789"];
NSMutableString *ms6 = [NSMutableString stringWithFormat:@"1234567890"];
NSLog(@"%@ : class = %@",ms1,NSStringFromClass([ms1 class]));
NSLog(@"%@ : class = %@",ms2,NSStringFromClass([ms2 class]));
NSLog(@"%@ : class = %@",ms3,NSStringFromClass([ms3 class]));
NSLog(@"%@ : class = %@",ms4,NSStringFromClass([ms4 class]));
NSLog(@"%@ : class = %@",ms5,NSStringFromClass([ms5 class]));
NSLog(@"%@ : class = %@",ms6,NSStringFromClass([ms6 class]));
//列印結果
2019-06-23 19:34:08.521931+0800 BlockDemo[49465:802590] : class = __NSCFString
2019-06-23 19:34:08.522058+0800 BlockDemo[49465:802590] str : class = __NSCFString
2019-06-23 19:34:08.522131+0800 BlockDemo[49465:802590] str : class = __NSCFString
2019-06-23 19:34:08.522196+0800 BlockDemo[49465:802590] str : class = __NSCFString
2019-06-23 19:34:08.522281+0800 BlockDemo[49465:802590] 123456789 : class = __NSCFString
2019-06-23 19:34:08.522372+0800 BlockDemo[49465:802590] 1234567890 : class = __NSCFString
從結果我們可以看出來NSMutableString都是分配在堆區,且是__NSCFString類型,NSString中Format相關方法也是都分配在堆區,但是會根據字元串的長度,區分為__NSCFString和NSTaggedPointerString兩種。在分配堆區的這些變數,其實一部分是正常的對象,一部分變成autorelease對象,具體是哪些,我們可以使用_objc_autoreleasePoolPrint()列印出來,比如實例中的g、ms4、ms5、ms6。
參考:
引用計數帶來的一次討論
Objective-C 引用計數原理
各個線程 Autorelease 對象的記憶體管理
Practical Memory Management
iOS記憶體管理
Xcode 10 下如何創建可調試的objc4-723、objc4-750.1工程
Block技巧與底層解析
Objective-C Autorelease Pool 的實現原理
《招聘一個靠譜的 iOS》
iOS 中 weak 的實現原理
iOS 底層解析weak的實現原理
weak的生命周期:具體實現方法
iOS weak 關鍵字漫談
Objective-C weak 弱引用實現
NSString記憶體管理
NSString的記憶體管理問題
iOS開發--引用計數與ARC