iOS使用頻率最高的四種記憶體管理

iOSeryh94發表於2020-06-20


一、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++實現,就可以看到轉換完的結果,如下所示:

// globalBlockstruct __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;
  }};...// stackBlockstruct __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 0x20d080objc[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 0x1151d75c0objc[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)
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 0x107b0d5c0objc[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 0x11331a5c0objc[1555]: 2 releases pending.0x2196ee78f1e100fdobjc[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 0x11331a5c00x2196ee78f1e100fdobjc[1555]: 5 releases pending.objc[1555]: [0x7fc2a9802000]  ................  PAGE  (hot) (cold)objc[1555]: [0x7fc2a9802038]    0x600002dbb600  __NSArrayI
objc[1555]: [0x7fc2a9802040]    0x600001bd8a50  __NSSetI
objc[1555]: [0x7fc2a9802048]  ################  POOL 0x7fc2a9802048objc[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 0x11331a5c00x2196ee78f1e100fdobjc[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);
    }}

作為一個開發者,有一個學習的氛圍跟一個交流圈子特別重要,這是一個我的iOS交流群: 519832104 不管你是小白還是大牛歡迎入駐,分享經驗,討論技術,大家一起交流學習成長!

另附上一份各好友收集的大廠面試題,需要iOS開發學習資料、面試真題,可以新增iOS開發進階交流群,進群可自行下載!

iOS使用頻率最高的四種記憶體管理

三、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 = __NSCFConstantString2019-06-23 19:23:13.240764+0800 BlockDemo[47229:789011]  : class = __NSCFConstantString2019-06-23 19:23:13.240870+0800 BlockDemo[47229:789011] str : class = __NSCFConstantString2019-06-23 19:23:13.240957+0800 BlockDemo[47229:789011] str : class = __NSCFConstantString

2)對於 __NSCFStringNSTaggedPointerString

  • __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 = NSTaggedPointerString2019-06-23 19:27:19.115286+0800 BlockDemo[48129:794364] str : class = NSTaggedPointerString2019-06-23 19:27:19.115388+0800 BlockDemo[48129:794364] 123456789 : class = NSTaggedPointerString2019-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 = __NSCFString2019-06-23 19:34:08.522058+0800 BlockDemo[49465:802590] str : class = __NSCFString2019-06-23 19:34:08.522131+0800 BlockDemo[49465:802590] str : class = __NSCFString2019-06-23 19:34:08.522196+0800 BlockDemo[49465:802590] str : class = __NSCFString2019-06-23 19:34:08.522281+0800 BlockDemo[49465:802590] 123456789 : class = __NSCFString2019-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。

點選此處,立即與iOS大牛交流學習

0人點贊


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69977274/viewspace-2699634/,如需轉載,請註明出處,否則將追究法律責任。

相關文章