我開始學習iOS的時候,已經有ARC這個東西了,所以一開始就是在ARC的環境下學習,雖然對於內存管理有了解,但並沒認真去處理這方面的問題。工作中的項目是以前開始開發,使用的是非ARC,而且項目已經初步成型,不好改成ARC,所以我又回頭去研究內存管理。開始時很暈,不知那些地方改retain、那些地方該release,不過後來清楚了 許多,而且感覺挺喜歡這些東西,感覺對於程序的運行對了一個角度的認識,覺得很不錯。以後新建項目,估計很少會不用ARC,只是覺得內存管理還是挺有意思的,而且是自己用心研究了的,就總結下。
1、結構:
從對象的角度來看整個程序,是一個嵌套的結構,首先是一個A,這個A的構建運行過程中又會用到許多其他的對象,而其他的對象再又會用到一些其他的對象,其實很像json的結構。為什麼要先這樣去看程序呢?這樣可以把整個程序看成是一個對象包含了許多其他對象的結構,這樣內存管理就可以分解為兩部分:(1)需要釋放的對象釋放掉 (2)一個對象釋放的時候,它所用到得對象都改被釋放(除了某些還被其他對象使用的對象)
舉例說就是push到一個ViewController,然後再從那個ViewController返回,那麼這個viewController會釋放,這一般都不會出錯,但是這個Viewcontroller的成員變量都釋放了嗎?這個ViewController方法中構建的臨時對象都釋放了嗎?然後你可能在這個viewContrller裡面使用了一個自定義的對象,假設為pruductCell,那麼當你把改釋放這個對象的地方都處理好了,就可以再去看看它的.m文件,是否它使用的對象也釋放正確了。
我覺得從這樣一個結構去檢查挺好的。
2、一個對象釋放的時候,它所用到得對象都改被釋放(除了某些還被其他對象使用的對象):
首先,對象的引用和釋放要遵循:你retain的你要release,不是你retain的不要release(假設為准則1)
一個對象(假設為A)使用到的其他對象,可以分為兩類:一個是臨時對象(假設為B),用完就可以釋放;還一個是對象的屬性、成員變量(假設為C),這個一般都會在對象A各個方法中被重復用到,這樣就可以等到對象A被釋放的時候再釋放,也就是在A的dealloc方法裡面釋放。
臨時對象:
這個很好處理,用完了就釋放掉:
UIView * subView = [[UIView alloc]initWithFrame:CGRectMake(30, 100, 150, 260)]; [self.view addSubview:subView]; //addsubView會retain子視圖 [subView release];這裡說的釋放不是值釋放內存,是指釋放掉對這個臨時對象的擁有權,就是執行release。對於擁有權,我的理解是對象A對某個對象(假設為B)進行了retain(包括alloc、copy等),這樣其它引用了這個對象的對象(假設為C),只要遵循准則1,當對象C release了對象B之後,對象B又會回到之前的引用計數。這樣只要對象A不release對象B,B的內存就永遠不會釋放。這就像對象A是擁有了對象B。
而對臨時對象release,這樣當引用它的對象也release它之後,它就沒有被任何對象擁有,內存就會被釋放,這樣剛好。例如這裡,當subView被從self.view上面移除後,基本就是不需要用這個subView的時候,這時它內存被釋放,剛好。
因為臨時變量構建在方法裡,出了這個方法就這個指針就沒了,就沒法釋放對它的所有權了,所以必須在方法結束前release。
屬性、成員變量:
為了在對象(A)存在的整個過程中,它的屬性、成員變量都活著,必須不能release,否則可能會引用計數為0,從而導致下次用的時候它內存已經被釋放了,從而程序崩潰:
_subView = [[UIView alloc]initWithFrame:CGRectMake(30, 100, 150, 260)]; [self.view addSubview:_subView]; [_subView release]; [_subView removeFromSuperview]; NSLog(@"%@",_subView); //程序崩潰不是說成員變量就不釋放,而是釋放了,下次再用就沒有了。而既然是成員變量、屬性,那麼就是會再多個方法裡面用到的,甚至會被其他的類的對象使用,所以要保持它是“活的”。
3、關於屬性定義時的關鍵詞retain、assign:
使用屬性定義變量時,自帶set\get方法,如果在變量賦值的時候使用self.XXX = YYY來方式賦值,其實是調用了set方法,而與內存管理有關的問題是:如果屬性定義時使用retain,set方法是:
-(void )setXXX:(UIView *)XXX{ [XXX retain]; [_XXX release]; _XXX = XXX; }對新值會retain,對舊值會release,從而使得對傳入的YYY獲取擁有權,保證在self使用它期間,它的內存永不會被釋放掉。這也遵循准則1。而使用assign卻是簡單賦值,沒有retain\release的操作。
如果屬性是assign或者干脆沒有使用self.XXX進行賦值,那麼就不會retain賦值對象,那麼:
UIView * view1 = [[UIView alloc]initWithFrame:CGRectMake(30, 100, 150, 260)]; _XXX = view1; [view1 release];這裡不會崩潰,但是接下來_XXX就變成了僵屍指針了,它指向位置的內存被釋放了,這也是assgin脆弱的地方,所以需要較長時間對某個對象的使用,最好使用retain,保證引用計數不會為0。
4、關於autoRelease:
首先類方法構建對象,返回值一般自帶autoRelease,使用時注意下。
為什麼要使用autoRelease,我的理解是延緩release。因為我構建的,所以我要釋放,但是如果我release了,這個對象內存就被釋放了,那不就白構建了嗎?可以構建,然後把它給需要使用的地方,然後再釋放。比如帶放回值得方法:
-(NSString *)createString{ NSString * str = [[NSString alloc]init]; return str; }如果我在方法裡面不釋放,然後外面使用的時候遵循准則1,那麼最後這個str的引用計數會保持在1,不會被釋放,這樣很成問題。但是如果釋放了,那return的就是空值,沒意義了。autoRelease使用了,就可以return一個有意義的值,然後最後這個對象在自動釋放池裡能被釋放,不用擔心內存洩露。
5、特殊的方法和對象:
addSubview會對添加進來的視圖retain,NSMutableArray的adObject也會retain添加的對象,block會對裡面使用到的變量全部retain,很可能造成循環引用,NStimer在沒有釋放的時候,會retain構建是指定的target,想要釋放掉target,就需要先停止NSTimer。
字符串常量不需要我們管理內存。