iOS內存管理詳解

目錄

  • block內存管理
  • autorelease內存管理
  • weak對象內存管理
  • NSString內存管理
  • new伤极、alloc、copy磁餐、mutableCopy對象內存管理(后續(xù)更新)
  • 屬性內存管理(后續(xù)更新)

一饵较、block內存管理

1.block內存類型

block內存分為三種類型:

  • _NSConcreteGlobalBlock(全局)
  • _NSConcreteStackBlock(棧)
  • _NSConcreteMallocBlock(堆)
2.三種類型的內存的創(chuàng)建時機

1)對于_NSConcreteStackBlock_NSConcreteGlobalBlock類型
_NSConcreteStackBlock_NSConcreteGlobalBlock這兩種類型的block,我們可以手動創(chuàng)建输拇,如下所示:

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++實現(xiàn)淳附,就可以看到轉換完的結果,如下所示:

// 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類型蠢古,即在全局區(qū)域創(chuàng)建,block變量存儲在全局數(shù)據(jù)存儲區(qū)别凹;stackBlock是_NSConcreteStackBlock類型草讶,即在棧區(qū)創(chuàng)建。
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);

對于作為參數(shù)傳遞的block缀旁,其類型是什么呢?

int i = 10;
void (^block)() = ^{i;};
__weak void (^weakBlock)() = ^{i;};
void (^stackBlock)() = ^{};
// ARC情況下
    
// 創(chuàng)建時勺鸦,都會在棧中
// <__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>

我們可以發(fā)現(xiàn)函數(shù)參數(shù)的block為什么類型剃执,block在函數(shù)中就是什么類型。

二吻谋、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//系統(tǒng)創(chuàng)建對象
objc[96400]: [0x7fae43000040]    0x600000c358b0  __NSSetI//系統(tǒng)創(chuàng)建對象
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()
//打印引用計數(shù)
_objc_rootRetainCount(obj)
2、autoreleasePool什么時候創(chuàng)建的瘫拣,里面的對象又是什么時候釋放的亿絮?

1)系統(tǒng)通過runloop創(chuàng)建的autoreleasePool
runloop 可以說是iOS 系統(tǒng)的靈魂。內存管理/UI 刷新/觸摸事件這些功能都需要 runloop 去管理和實現(xiàn)麸拄。runloop是通過線程創(chuàng)建的派昧,和線程保持一對一的關系,其關系是保存在一個全局的 Dictionary 里拢切。線程剛創(chuàng)建時并沒有 RunLoop蒂萎,如果你不主動獲取,那它一直都不會有淮椰。RunLoop 的創(chuàng)建是發(fā)生在第一次獲取時五慈,RunLoop 的銷毀是發(fā)生在線程結束時。你只能在一個線程的內部獲取其 RunLoop(主線程除外)主穗。

runloop和autoreleasePool又是什么關系呢豺撑?對象又是什么時候釋放的?

App啟動后黔牵,蘋果在主線程 RunLoop 里注冊了兩個 Observer聪轿,其回調都是 _wrapRunLoopWithAutoreleasePoolHandler()。

第一個 Observer 監(jiān)視的事件是 Entry(即將進入Loop)猾浦,其回調內會調用 _objc_autoreleasePoolPush() 創(chuàng)建自動釋放池陆错。其 order 是-2147483647,優(yōu)先級最高金赦,保證創(chuàng)建釋放池發(fā)生在其他所有回調之前音瓷。

第二個 Observer 監(jiān)視了兩個事件: BeforeWaiting(準備進入休眠) 時調用_objc_autoreleasePoolPop() 和 _objc_autoreleasePoolPush() 釋放舊的池并創(chuàng)建新池;Exit(即將退出Loop) 時調用 _objc_autoreleasePoolPop() 來釋放自動釋放池夹抗。這個 Observer 的 order 是 2147483647绳慎,優(yōu)先級最低,保證其釋放池子發(fā)生在其他所有回調之后。

在主線程執(zhí)行的代碼杏愤,通常是寫在諸如事件回調靡砌、Timer回調內的。這些回調會被 RunLoop 創(chuàng)建好的 AutoreleasePool 環(huán)繞著珊楼,所以不會出現(xiàn)內存泄漏通殃,開發(fā)者也不必顯示創(chuàng)建 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 {}方式手動創(chuàng)建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對象的管理?
線程剛創(chuàng)建時并沒有 RunLoop兢仰,如果你不主動獲取,那它一直都不會有剂碴。所以在我們創(chuàng)建子線程的時候把将,如果沒有獲取runloop,那么也就沒用通過runloop來創(chuàng)建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.如何實現(xiàn)
Runtime維護了一個weak表菲嘴,用于存儲指向某個對象的所有weak指針,對于 weak 對象會放入一個 hash 表中,Key是所指對象的地址,Value是weak指針的地址(這個地址的值是所指對象的地址)數(shù)組龄坪。 當此對象的引用計數(shù)為0的時候會 dealloc昭雌,假如 weak 指向的對象內存地址是a,那么就會以a為鍵悉默, 在這個 weak 表中搜索城豁,找到所有以a為鍵的 weak 對象,從而設置為 nil抄课。
注:由于可能多個weak指針指向同一個對象唱星,所以value為一個數(shù)組

weak 的實現(xiàn)原理可以概括以下三步:

1)初始化時:runtime會調用objc_initWeak函數(shù),初始化一個新的weak指針指向對象的地址跟磨。
我們以下面這行代碼為例:

代碼清單1:示例代碼

{
    id __weak obj1 = obj;
}

當我們初始化一個weak變量時间聊,runtime會調用objc_initWeak函數(shù)。這個函數(shù)在Clang中的聲明如下:

id objc_initWeak(id *object, id value);

其具體實現(xiàn)如下:

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作為參數(shù)傳遞給objc_storeWeak函數(shù)。
objc_initWeak函數(shù)有一個前提條件:就是object必須是一個沒有被注冊為__weak對象的有效指針僵蛛。而value則可以是null尚蝌,或者指向一個有效的對象。

2)添加引用時:objc_initWeak函數(shù)會調用 objc_storeWeak() 函數(shù)充尉。
objc_storeWeak() 的作用是更新指針指向飘言,創(chuàng)建對應的弱引用表。

3)釋放時驼侠,調用clearDeallocating函數(shù)姿鸿。
clearDeallocating函數(shù)首先根據(jù)對象地址獲取所有weak指針地址的數(shù)組,然后遍歷這個數(shù)組把其中的數(shù)據(jù)設為nil倒源,最后把這個entry從weak表中刪除苛预,最后清理對象的記錄。

四笋熬、NSString內存管理

1.NSString內存的類型

NSString內存分為兩種類型:

  • __NSCFConstantString(常量區(qū))
  • __NSCFString(堆區(qū))热某、NSTaggedPointerString(堆區(qū))
2.兩種內存類型的創(chuàng)建時機。

生成一個NSString類型的字符串有三種方法:

  • 方法1.直接賦值:
 NSString *str1 = @"my string"; 
  • 方法2.類函數(shù)初始化生成:
NSString *str2 = [NSString stringWithString:@"my string"];
  • 方法3.實例方法初始化生成:
NSString *str3 = [[NSString alloc] initWithString:@"my string"];
NSString *str4 = [[NSString alloc]initWithFormat:@"my string"];

1)對于__NSCFConstantString
這種類型的字符串是常量字符串胳螟。該類型的字符串以字面量的方式創(chuàng)建苫拍,保存在字符串常量區(qū),是在編譯時創(chuàng)建的旺隙。

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)對于__NSCFStringNSTaggedPointerString

  • __NSCFString 表示對象類型的字符串绒极,在運行時創(chuàng)建,保存在堆區(qū)蔬捷,初始引用計數(shù)為1垄提,其內存管理方式就是對象的內存管理方式榔袋。
  • NSTaggedPointerString是對__NSCFString類型的一種優(yōu)化,在運行創(chuàng)建字符串時铡俐,會對字符串內容及長度作判斷凰兑,若內容由ASCII字符構成且長度較小(具體要多小暫時不太清楚)审丘,這時候創(chuàng)建的字符串類型就是 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都是分配在堆區(qū)吏够,且是__NSCFString類型,NSString中Format相關方法也是都分配在堆區(qū)滩报,但是會根據(jù)字符串的長度锅知,區(qū)分為__NSCFString和NSTaggedPointerString兩種。在分配堆區(qū)的這些變量脓钾,其實一部分是正常的對象售睹,一部分變成autorelease對象,具體是哪些可训,我們可以使用_objc_autoreleasePoolPrint()打印出來昌妹,比如實例中的g、ms4握截、ms5飞崖、ms6。

參考:
引用計數(shù)帶來的一次討論
Objective-C 引用計數(shù)原理
各個線程 Autorelease 對象的內存管理
Practical Memory Management
iOS內存管理
Xcode 10 下如何創(chuàng)建可調試的objc4-723谨胞、objc4-750.1工程
Block技巧與底層解析
Objective-C Autorelease Pool 的實現(xiàn)原理
《招聘一個靠譜的 iOS》
iOS 中 weak 的實現(xiàn)原理
iOS 底層解析weak的實現(xiàn)原理
weak的生命周期:具體實現(xiàn)方法
iOS weak 關鍵字漫談
Objective-C weak 弱引用實現(xiàn)
NSString內存管理
NSString的內存管理問題
iOS開發(fā)--引用計數(shù)與ARC

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末固歪,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子畜眨,更是在濱河造成了極大的恐慌,老刑警劉巖术瓮,帶你破解...
    沈念sama閱讀 218,682評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件康聂,死亡現(xiàn)場離奇詭異,居然都是意外死亡胞四,警方通過查閱死者的電腦和手機恬汁,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,277評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來辜伟,“玉大人氓侧,你說我怎么就攤上這事〉冀疲” “怎么了约巷?”我有些...
    開封第一講書人閱讀 165,083評論 0 355
  • 文/不壞的土叔 我叫張陵,是天一觀的道長旱捧。 經(jīng)常有香客問我独郎,道長踩麦,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,763評論 1 295
  • 正文 為了忘掉前任氓癌,我火速辦了婚禮谓谦,結果婚禮上,老公的妹妹穿的比我還像新娘贪婉。我一直安慰自己反粥,他們只是感情好,可當我...
    茶點故事閱讀 67,785評論 6 392
  • 文/花漫 我一把揭開白布疲迂。 她就那樣靜靜地躺著才顿,像睡著了一般。 火紅的嫁衣襯著肌膚如雪鬼譬。 梳的紋絲不亂的頭發(fā)上娜膘,一...
    開封第一講書人閱讀 51,624評論 1 305
  • 那天,我揣著相機與錄音优质,去河邊找鬼竣贪。 笑死,一個胖子當著我的面吹牛巩螃,可吹牛的內容都是我干的演怎。 我是一名探鬼主播,決...
    沈念sama閱讀 40,358評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼避乏,長吁一口氣:“原來是場噩夢啊……” “哼爷耀!你這毒婦竟也來了?” 一聲冷哼從身側響起拍皮,我...
    開封第一講書人閱讀 39,261評論 0 276
  • 序言:老撾萬榮一對情侶失蹤歹叮,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后铆帽,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體咆耿,經(jīng)...
    沈念sama閱讀 45,722評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年爹橱,在試婚紗的時候發(fā)現(xiàn)自己被綠了萨螺。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,030評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡愧驱,死狀恐怖慰技,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情组砚,我是刑警寧澤吻商,帶...
    沈念sama閱讀 35,737評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站糟红,受9級特大地震影響手报,放射性物質發(fā)生泄漏蚯舱。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,360評論 3 330
  • 文/蒙蒙 一掩蛤、第九天 我趴在偏房一處隱蔽的房頂上張望枉昏。 院中可真熱鬧,春花似錦揍鸟、人聲如沸兄裂。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,941評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽晰奖。三九已至,卻和暖如春腥泥,著一層夾襖步出監(jiān)牢的瞬間匾南,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,057評論 1 270
  • 我被黑心中介騙來泰國打工蛔外, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留蛆楞,地道東北人。 一個月前我還...
    沈念sama閱讀 48,237評論 3 371
  • 正文 我出身青樓夹厌,卻偏偏與公主長得像豹爹,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子矛纹,可洞房花燭夜當晚...
    茶點故事閱讀 44,976評論 2 355

推薦閱讀更多精彩內容