RunLoop和autorelease的一道面試題,runloopautorelease
有這麼一道ios面試題 以下代碼有沒有什麼問題?如果有?如何解決?
for (int i = 0 ; i < largeNumber; i++) {NSString *str = [NSString stringWithFormat:@"hello -%04d",i];str = [str stringByAppendingString:@" - world"]; }
局部釋放池和RunLoop釋放池的概念:
主線程的RunLoop是預設開啟的(視圖用[[NSRunLoop currentRunLoop] runUntilDate:[NSDate date]]來停止它,也是做不到的), 每一次訊息迴圈開始的時候會先建立自動釋放池,這次迴圈結束前,會釋放自動釋放池,然後RunLoop等待下次事件來源。 在這個過程中,由RunLoop建立的釋放池類似於一個全域的釋放池。但是開發人員可以任何執行的地方建立釋放池,也就是局部的釋放池,這時的釋放池類似於代碼塊 當釋放池結束的時候會自動釋放。因此一般情況下,局部的自動釋放池很快就被釋放了,而RunLoop釋放池會等一次訊息迴圈結束的時候釋放。
什麼樣的對象會交給釋放池管理:
返回當前類的執行個體的類方法建立出來的對象,都是autorelease的,會交給所在的釋放池進行管理。 例如建立一個Person類,使用[[self alloc]init]方法建立的對象的管理不會交給它所在的釋放池,而是根據引用計數來控制釋放的時機, 如果使用[[[self alloc]init] autorelease]建立的對象,會交給所在的釋放池管理,控制其釋放的時機。
- (void)test{@autoreleasepool { Person *p = [[Person alloc]init]; p = nil; NSLog(@"---"); } NSLog(@"autorelease結束");}
執行結果:
Person---dealloc---autorelease結束
- (void)test1{ @autoreleasepool { Person *p = [Person person]; // 內部是[[[self alloc]init] autorelease] p = nil; NSLog(@"---"); } NSLog(@"autorelease結束");}
執行的結果為:
---Person---deallocautorelease結束
因此自動釋放池被銷毀或耗盡時會向池中所有使用autorelease建立的對象發送release 訊息,釋放所有autorelease的對象,而不是所有的對象。
回到面試的問題:
當我們使用for迴圈建立很多個使用autorelease方式建立的NSString對象的時候,將所有的對象的釋放權都交給了RunLoop 的釋放池,而RunLoop的釋放池會等待這個事件處理之後才會釋放,因此就會使對象無法及時釋放,堆積在記憶體造成記憶體泄露,可以在Debug Navigation 中觀察到記憶體激增。為了驗證確實是因為autorelease這種建立方式引起的記憶體泄露,我做了如下的測試:
int largeNumber = 100000000;- (void)test3{ for (int i = 0 ; i < largeNumber; i++) { NSString *str = [[NSString alloc]initWithFormat:@"hello -%04d",i]; str = [str stringByAppendingString:@" - world"]; }}// 這樣做的結果是記憶體幾乎沒有變化,驗證了確實是這個原因。
但是在編寫代碼的時候我們仍然習慣用類的快速建立方法,而不是alloc+init。因此解決的方案就是添加局部的釋放池,以及時釋放記憶體 如果將局部釋放池添加到迴圈外:
- (void)test4{ @autoreleasepool { for (int i = 0 ; i < largeNumber; i++) { NSString *str = [[NSString alloc]initWithFormat:@"hello -%04d",i]; str = [str stringByAppendingString:@" - world"]; } }}
這樣顯然是沒有效果的,釋放池需要等迴圈執行之後再釋放記憶體,這和使用RunKLoop建立的釋放池沒有什麼區別。 較好的方案就是每次迴圈的時候添加一個釋放池:
- (void)test5{ for (int i = 0 ; i < largeNumber; i++) { @autoreleasepool { NSString *str = [[NSString alloc]initWithFormat:@"hello -%04d",i]; str = [str stringByAppendingString:@" - world"]; } }}
這樣每一次迴圈的結束時都會釋放一次記憶體,因而這個迴圈全部執行完成時也幾乎補不消耗記憶體。
總結是:
做多線程開發時,需要線上程調度方法中手動添加自動釋放池,尤其是當執行迴圈的時候,如果迴圈內部有使用類的快速建立方法建立的對象, 一定要將迴圈體放到自動釋放池中。