C#妹妹:記憶體的清理跟生活中的拆遷一樣是個麻煩事情啊
Objective-C阿姨:是啊,該拆的不拆,佔用空間,不該拆的拆了,程式崩潰
C#妹妹:說白了,難度不在拆上,在判定上,判斷到底那些記憶體是用不上的。
Objective-C阿姨:沒錯,就像現實生活中的拆遷,扒房子不困難,推土機過來就好了,困難的是決定扒誰的房子。。。扒對了相安無事,扒錯了弄個自焚的出來⋯⋯
C#妹妹:做個廣告,.NET的記憶體回收機制是相當不錯的。判斷很準確~
Objective-C阿姨:沒錯,但是也要付出代價,依靠運行時檢查廢棄的對象,就好像依靠人口普查來確定那些房子沒人用,是靠定時遍曆來實現的,畢竟影響效能,並且回收也不可能那麼及時。
C#妹妹:是的,記憶體回收其實是兩部分工作,一個是“檢查”,一個是“回收”,“檢查”就是找到那些沒人用的房子,在牆上寫一個大大的被圓圈圈起來的“拆”字。“回收”就是把標有“拆”字的房子推平,並且把還在用的房子集中在一起,避免形成片段。還有很重要的一點是,回收過程中程式是暫停狀態的。
Objective-C阿姨:寫拆字的國際慣例也符合啊?~整個過程好像很漫長,效能如何保證呢?
C#妹妹:.NET的運行時為了提高效率採取了很多方法。
首先它優先普查人口流動比較大的地區,人口流動大,意味著房子閑置的可能性比較大。這個主要通過代齡來最佳化的,對象佔用的空間,每經過一次記憶體回收行程的掃描,而沒有被清理掉,代齡就加1。比如你04年買的房子,這時代齡為0,06年人口普查發現這套在用,代齡就變成1,08年人口普查有可能就不再檢查這套房子了,因為你已經起碼住了2年多了,搬家的可能性小些,記憶體回收行程會重點檢查上次人口普查之後新入住的那些房子。當然清理了0代的房子後,還沒有足夠的空間,記憶體回收行程還會去檢查1代甚至2代的房子的。
Objective-C阿姨:這就是歧視啊,怪不得我在上海買了房子也沒戶口,原來我是0代⋯⋯
C#妹妹:沒法子啊,誰讓效率優先呢?誰會在乎我們小百姓的公平~記憶體回收行程第二個提高效率的方法是減少普查的次數,除非程式佔用的記憶體超過規定,或者系統本身記憶體不富裕,不會輕易去搞記憶體普查這些爛事的。你以為記憶體回收行程沒事幹就一直掃描啊,他們也是想多歇歇呢~
Objective-C阿姨:.NET記憶體回收也有這麼多內幕啊
C#妹妹:小聲點,你不想想,這年頭沒點內幕誰去拆遷啊?Objective-C阿姨,你的對象管理就沒有內幕?
Objective-C阿姨:嗯,有,不過相對比較河蟹~因為有那麼點選擇的餘地,上次不是說過了通過“Retain、Release”統計對象的引用數量來判斷對象是否可以回收,這種方法專業一點的名字叫“引用計數(reference counting)技術”。今天我繼續往後講“自動釋放(Auto Release)技術”。
C#妹妹:自動釋放?聽起來好像蠻先進的,是不是跟我的記憶體回收差不多。
Objective-C阿姨:差的多,它的本質還是引用計數,其實並不自動化,只是簡化了邏輯和代碼而已。算是個外援吧,還拿那個悲劇的House類做個實驗吧
#import "House.h"//先建一個需要被刪除的對象 House類@implementation House-(void) dealloc//Objective-C在銷毀對象的時候會自動調用這個方法{ NSLog(@"房子被拆除了"); [super dealloc];}@end
Objective-C阿姨:這個倒黴House類,只重載了dealloc這個方法,忘記了嗎?就是拆房子的時候會調用這個方法。接下來執行下面的代碼
int main (int argc, const char * argv[]){ NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];//建立一個自動釋放池 pool House *h1=[[House new] autorelease];//將產生的對象添加到自動釋放池中 NSLog(@"%lu",[h1 retainCount]);//對象的引用數量為1 NSLog(@"開始銷毀自動釋放池"); [pool drain];//銷毀自動釋放池,同時也銷毀自動釋放池中的對象,包括h1引用的House對象 return 0;}
執行結果
Objective-C阿姨:一句話一句話看吧,首先是
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
聽起來貌似很酷,又是自動,又是池,其實沒啥技術含量,原理其實是個類似NSMutableArray的東西,它的dealloc方法裡有向池中所有對象發送release訊息的代碼,所以銷毀池的時候,會執行池的dealloc方法,池向所有對象發送release。就這麼簡單。
至於[[NSAutoreleasePool alloc] init] 相當於 [NSAutoreleasePool new],就是初始化對象。
說白了就是執行個體化了類“NSAutoreleasePool”的一個對象
C#妹妹:我看也是,上次你說過的NS開頭表示Cocoa的類,這個類就是名字稍微長點,要是豎著寫可能還襯的個頭高點,可惜是趴著的,確實沒覺得比其他類長的帥⋯⋯
Objective-C阿姨:House *h1=[[House new] autorelease]這句話重點在autorelease,意思是把House的執行個體,添加到NSAutoreleasePool中。也可以這樣寫
House *h1=[House new]; [h1 autorelease];
這一步僅僅是把對象添加到池中,並沒有減少引用,所以統計retainCount的時候,仍然為1
接下來最後一步
[pool drain]
drain和release意思差不多,就是清空/銷毀自動釋放池,同時銷毀池中的對象。
但是這裡說“銷毀池中的對象”其實很不準確
準確的說法是逐個給池中的對象發送release訊息,在看看剛才我說的原理。
因為經過這一步,池中對象僅僅是引用數量減1(也就是調用了release),能否銷毀要看減1後引用數量是否為0
比如看下面的例子
int main (int argc, const char * argv[]){ NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];//建立一個自動釋放池 pool House *h1=[[House new] autorelease];//將產生的對象添加到自動釋放池中 [h1 retain];//手工添加一個引用 NSLog(@"%lu",[h1 retainCount]);//對象的引用數量為2 NSLog(@"開始銷毀自動釋放池"); [pool drain]; //這個時候h1引用的對象並沒有被銷毀 發生記憶體泄露 NSLog(@"記憶體泄露啦:%@",h1); return 0;}
執行結果
因為我們之前手工通過retain給h1引用的對象增加了一個引用,導致即使[pool drain]也沒有銷毀對象
所以說AutoreleasePool並沒有幫我們真正的把池中的對象銷毀,只是給池中對象逐個調用release而已。
再以圖片的形式看看記憶體中發生的事情
C#妹妹:這樣看下來,自動釋放池並沒有解決什麼問題呀?不是還要我們自己管理引用數量嗎?
Objective-C阿姨:確實還是需要我們自己統計引用數量,但是可以幫我們從亂七八糟的release中解脫出來。只需要release池對象就好了,其他對象也就跟著被release了
int main (int argc, const char * argv[]){ NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];//建立一個自動釋放池 pool House *h1=[[House new] autorelease]; House *h2=[[House new] autorelease]; House *h3=[[House new] autorelease]; House *h4=[[House new] autorelease]; NSLog(@"%lu,%lu,%lu,%lu",[h1 retainCount],[h2 retainCount],[h3 retainCount],[h4 retainCount]); //h1-h4經過某些運算 完成了曆史使命 [pool drain]; return 0;}
上面的代碼肯定比下面的這些更清爽點
int main (int argc, const char * argv[]){ House *h1=[House new]; House *h2=[House new]; House *h3=[House new]; House *h4=[House new]; NSLog(@"%lu,%lu,%lu,%lu",[h1 retainCount],[h2 retainCount],[h3 retainCount],[h4 retainCount]); //h1-h4經過某些運算 完成了曆史使命 [h1 release]; [h2 release]; [h3 release]; [h4 release]; return 0;}
C#妹妹:貌似是少調用了些release,但是畢竟沒有release靈活啊,如果是release,一旦程式員決定不在使用的對象,馬上就可以釋放掉,但是AutoreleasePool必須要等到最後一起drain。說白了也就是延長了垃圾的存留時間,浪費了記憶體。
Objective-C阿姨:一點不假,所以在行動裝置上 比如iPhone,iPad,為了節約寶貴的記憶體資源,不建議使用自動釋放池。即便在Mac上啟動並執行程式,如果有太多的對象產生,也可以多次產生釋放或嵌套對象池來儘早釋放對象。比如下面的例子
int main (int argc, const char * argv[]){ NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; int i; for(i=0;i<1000000000;i++) { House *h=[[House new] autorelease]; if(i%1000==0)//如果沒有這個if語句裡邊的內容,所有1000000000個對象要全部產生完畢才能全部釋放 { [pool drain]; NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; } } [pool drain]; return 0;}
比如上面的代碼,如果沒有if語句裡邊的內容,所有1000000000個House對象都要等到全部對象產生完畢,程式結束的時候才能全部釋放
記憶體消耗量非常大
但是加了這個if語句,每產生1000個對象,銷毀一次,記憶體佔用就非常小。
咳,十億套房子要真的就這麼產生了,非氣死那些開發商不可。。。
C#妹妹:
Objective-C阿姨:說了半天,還沒有說自動釋放池最重要的用法。
C#妹妹:最重要??你認為有人會耐著性子把咱倆的對話看到這個地方嗎?
Objective-C阿姨:剛才說嗨了,重點忘記了。也許有那麼1-2人能耐著性子看到這裡吧,大部分人早把網頁關閉了。看下面的例子
#import "House.h"//先建一個需要被刪除的對象 House類@implementation House-(void) dealloc//Objective-C在銷毀對象的時候會自動調用這個方法{ NSLog(@"房子被拆除了"); [super dealloc];}-(NSString*)description{ NSString *desc=[[NSString alloc] initWithFormat:@"世界上最便宜的房子"]; return desc;}@end
我修改了一下House類,添加了一個description方法,返回一個NSString對象,
description相當於.NET中的ToString()方法,其實也是基類NSObject定義的,子類可以重寫,用來返回描述該類的字串。
我這裡重新了description,返回NSString對象,但是這個NSString對象該如何銷毀呢?該由誰來負責release呢?
顯然這個工作只能由調用description的程式來完成
C#妹妹:廢話,要是在description裡邊就銷毀了,還返回個屁結果啊
Objective-C阿姨:那就看下面的例子吧。
int main (int argc, const char * argv[]){ NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; House *h1=[[House new]autorelease]; NSString *desc=[h1 description]; NSLog(@"%@",desc); [desc release]; [pool drain]; return 0;}
這樣用起來畢竟不直接
如果在House的description中使用autorelease,比如代碼修改為
NSString *desc=[[NSString alloc] initWithFormat:@"世界上最便宜的房子"];return [desc autorelease];
調用House的description的程式就可以更簡潔
int main (int argc, const char * argv[]){ NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; House *h1=[[House new]autorelease]; NSLog(@"%@",[h1 description]); [pool drain]; return 0;}
C#妹妹:今天好像好像暈了⋯⋯感覺越來越複雜了~~,程式員管理記憶體還真是很辛苦⋯⋯
Objective-C阿姨:今天說了太多的技術,因為沒有規則來約束他們,所以感覺好像雜亂無章,下次會有記憶體管理的章法,只要遵循幾個簡單的原則,其實是非常簡單的。下次再聊哇。。。
--
各位同學,本人學習Objective-C時間很短,學習Objective-C其實不是為了Mac、iPhone開發,並沒有實用,
其實是一個C#使用者學習Objective-C的學習筆記,學習的確切目的是協助我理解C#,畢竟沒有比較是不可能知道所謂C#的特點的
請大家批判的眼光看這個東西,如果發現和其他文章、書籍、評論、資料有衝突,請盡量以其他文章為準。並給我留言
也邀請所有高手積極拍磚,我正好用來蓋房子~~~
《C#妹妹和Objective-C阿姨對話錄》
(01)認識Objective-C--初次見面的問候
(02)這就是類--阿姨的狗狗
(03)NSString--再遇狗狗
(04)記憶體回收基礎--拆遷隊那點事
(05)自動釋放池--拆遷隊的外援
待續⋯⋯