iOS開發的四種記憶體管理

来源:https://www.cnblogs.com/chengxyyh/archive/2020/06/20/13169137.html
-Advertisement-
Play Games

一、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)對於__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 = 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

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


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 1. 測試環境搭建步驟 為什麼要安裝tomcat、mysql? —所測試的項目需求環境決定。tomcat-項目容器(放置開發打包的項目代碼),mysql-數據倉庫。 為什麼要安裝jdk? —java軟體開發包(Java Development Kit),沒有JDK的話,無法編譯運行Java程式。 1 ...
  • 一 CLI訪問OpenShift資源 1.1 資源操作 OCP將OpenShift集群中的為由主節點管理的對象統稱為資源,如:node、service、pod、project、deployment、user。 即使針對的是不同的資源,OpenShift命令行工具也提供了一種統一的、一致的方法來更新、 ...
  • ffmpeg -i test.mp4 -codec copy -bsf: h264_mp4toannexb -f h264 test.264 從MP4文件內提取視頻流,忽略音頻流,指定幀頻、碼率 ffmpeg -i test.mp4 -vcodec h264 -an -r 25 -b:v 256k ...
  • 初用MySQL Mysql示例庫 Navicat15 查看初始密碼 MySQl首次啟動會創建“超級管理員賬號”root@localhost,初始密碼存儲在日誌文件中,通過grep搜索並查看: grep 'temporary password' /var/log/mysqld.log 進入mysql ...
  • 對於MySQL的一些個規範,某些公司建表規範中有一項要求是所有欄位非空,意味著沒有值的時候存儲一個預設值。其實所有欄位非空這麼說應該是絕對了,應該說是儘可能非空,某些情況下不可能給出一個預設值。那麼這條要求,是基於哪些考慮因素,存儲空間?相關增刪查改操作的性能?亦或是其他考慮?該理論到底有沒有道理或 ...
  • 1. 安裝資料庫 1) yum -y install mysql-server(簡單) yum命令自動從網上尋找mysql服務資源,下載至本地並完成安裝 2) 也可以自己在網上下載mysql服務,通過xftp傳輸至Linux系統,自己安裝(一般安裝在usr或opt目錄下) 2. 啟動資料庫 安裝完畢 ...
  • MySQL中給一張千萬甚至更大量級的表添加欄位一直是比較頭疼的問題,遇到此情況通常該如果處理?本文通過常見的三種場景進行案例說明。 1、 環境準備 資料庫版本: 5.7.25-28(Percona 分支) 伺服器配置: 3台centos 7虛擬機,配置均為2CPU 2G記憶體 資料庫架構: 1主2從的 ...
  • 一、我們創建一個新的android項目來進行演示廣播機制中是如何​顯示網路狀態的。 package com.example.broadcasttest2; import android.app.Activity; import android.content.BroadcastReceiver; i ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...