【版權聲明】html
此文版權歸做者Vince Yuan (vince.yuan#gmail.com)全部。歡迎非營利性轉載,轉載時必須包含原始連接http://vinceyuan.cnblogs.com/,且必須包含此版權聲明的完整內容。 ios
版本 1.1 發表於2010-03-08小程序
【注】segmentfault
本文會以做者Vince Yuan的文章內容爲基礎,本人添加括號備註。數組
前言xcode
初學objectice-C的朋友都有一個困惑,總以爲對objective-C的內存管理機制琢磨不透,程序常常內存泄漏或莫名其妙的崩潰。我在這裏總結了本身對objective-C內存管理機制的研究成果和經驗,寫了這麼一個由淺入深的教程。但願對你們有所幫助,也歡迎你們一塊兒探討。iphone
此文涉及的內存管理是針對於繼承於NSObject的Class。函數
一 基本原理工具
Objective-C的內存管理機制與.Net/Java那種全自動的垃圾回收機制是不一樣的,它本質上仍是C語言中的手動管理方式,只不過稍微加了一些自動方法。oop
1.Objective-C的對象生成於堆之上,生成以後,須要一個指針來指向它。
ClassA *obj1 = [[ClassA alloc] init];
2.Objective-C的對象在使用完成以後不會自動銷燬,須要執行dealloc來釋放空間(銷燬),不然內存泄露。
[obj1 dealloc];
這帶來了一個問題。下面代碼中obj2是否須要調用dealloc?
ClassA *obj1 = [[ClassA alloc] init]; ClassA *obj2 = obj1; [obj1 hello]; //輸出hello [obj1 dealloc]; [obj2 hello]; //可以執行這一行和下一行嗎? [obj2 dealloc];
不能,由於obj1和obj2只是指針,它們指向同一個對象,[obj1 dealloc]已經銷燬這個對象了,不能再調用[obj2 hello]和[obj2 dealloc]。obj2其實是個無效指針。
如何避免無效指針?請看下一條。
3.Objective-C採用了引用計數(ref count或者retain count)。對象的內部保存一個數字,表示被引用的次數。例如,某個對象被兩個指針所指向(引用)那麼它的retain count爲2。須要銷燬對象的時候,不直接調用dealloc,而是調用release。release會讓retain count減1,只有retain count等於0,系統纔會調用dealloc真正銷燬這個對象。
ClassA *obj1 = [[ClassA alloc] init]; //對象生成時,retain count = 1 [obj1 release]; //release使retain count減1,retain count = 0,dealloc自動被調用,對象被銷燬
咱們回頭看看剛剛那個無效指針的問題,把dealloc改爲release解決了嗎?
ClassA *obj1 = [[ClassA alloc] init]; //retain count = 1 ClassA *obj2 = obj1; //retain count = 1 [obj1 hello]; //輸出hello [obj1 release]; //retain count = 0,對象被銷燬 [obj2 hello]; [obj2 release];
[obj1 release]以後,obj2依然是個無效指針。問題依然沒有解決。解決方法見下一條。
4.Objective-C指針賦值時,retain count不會自動增長,須要手動retain。
ClassA *obj1 = [[ClassA alloc] init]; //retain count = 1 ClassA *obj2 = obj1; //retain count = 1 [obj2 retain]; //retain count = 2 [obj1 hello]; //輸出hello [obj1 release]; //retain count = 2 – 1 = 1 [obj2 hello]; //輸出hello [obj2 release]; //retain count = 0,對象被銷燬
問題解決!注意,若是沒有調用[obj2 release],這個對象的retain count始終爲1,不會被銷燬,內存泄露。(1-4能夠參考附件中的示例程序memman-no-pool.m)
這樣的確不會內存泄露,但彷佛有點麻煩,有沒有簡單點的方法?見下一條。
5.Objective-C中引入了autorelease pool(自動釋放對象池),在遵照一些規則的狀況下,能夠自動釋放對象。(autorelease pool依然不是.Net/Java那種全自動的垃圾回收機制)
5.1 新生成的對象,只要調用autorelease就好了,無需再調用release!
ClassA *obj1 = [[[ClassA alloc] init] autorelease]; //retain count = 1 但無需調用release
5.2 對於存在指針賦值的狀況,代碼與前面相似。
ClassA *obj1 = [[[ClassA alloc] init] autorelease]; //retain count = 1 ClassA *obj2 = obj1; //retain count = 1 [obj2 retain]; //retain count = 2 [obj1 hello]; //輸出hello //對於obj1,無需調用(實際上不能調用)release [obj2 hello]; //輸出hello [obj2 release]; //retain count = 2-1 = 1
細心的讀者確定能發現這個對象沒有被銷燬,什麼時候銷燬呢?誰去銷燬它?(能夠參考附件中的示例程序memman-with-pool.m)請看下一條。
6.autorelease pool原理剖析。(其實很簡單的,必定要堅持看下去,不然仍是不能理解Objective-C的內存管理機制。)
6.1 autorelease pool不是天生的,須要手動創立。只不過在新建一個iphone項目時,xcode會自動幫你寫好。autorelease pool的真名是NSAutoreleasePool。
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
6.2 NSAutoreleasePool內部包含一個數組(NSMutableArray),用來保存聲明爲autorelease的全部對象。若是一個對象聲明爲autorelease,系統所作的工做就是把這個對象加入到這個數組中去。
ClassA *obj1 = [[[ClassA alloc] init] autorelease]; //retain count = 1,把此對象加入autorelease pool中
6.3 NSAutoreleasePool自身在銷燬的時候,會遍歷一遍這個數組,release數組中的每一個成員。若是此時數組中成員的retain count爲1,那麼release以後,retain count爲0,對象正式被銷燬。若是此時數組中成員的retain count大於1,那麼release以後,retain count大於0,此對象依然沒有被銷燬,內存泄露。
6.4 默認只有一個autorelease pool,一般相似於下面這個例子。
int main (int argc, const char *argv[]) { NSAutoreleasePool *pool; pool = [[NSAutoreleasePool alloc] init]; // do something [pool release]; return (0); } // main
全部標記爲autorelease的對象都只有在這個pool銷燬時才被銷燬。若是你有大量的對象標記爲autorelease,這顯然不能很好的利用內存,在iphone這種內存受限的程序中是很容易形成內存不足的。例如:
int main (int argc, const char *argv[]) { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; int i, j; for (i = 0; i < 100; i++ ) { for (j = 0; j < 100000; j++ ) [NSString stringWithFormat:@"1234567890"];//產生的對象是autorelease的。 } [pool release]; return (0); } // main
(能夠參考附件中的示例程序memman-many-objs-one-pool.m,運行時經過監控工具能夠發現使用的內存在急劇增長,直到pool銷燬時才被釋放)你須要考慮下一條。
7.Objective-C程序中能夠嵌套建立多個autorelease pool。在須要大量建立局部變量的時候,能夠建立內嵌的autorelease pool來及時釋放內存。(感謝網友hhyytt和neogui的提醒,某些狀況下,系統會自動建立autorelease pool, 請參見第四章)
int main (int argc, const char *argv[]) { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; int i, j; for (i = 0; i < 100; i++ ) { for (j = 0; j < 100000; j++ ) [NSString stringWithFormat:@"1234567890"];//產生的對象是autorelease的。 } [pool release]; return (0); } // main
(能夠參考附件中的示例程序memman-many-objs-many-pools.m,佔用內存的變化極小)
二 口訣與範式
1.口訣
1.1 誰建立,誰釋放(相似於「誰污染,誰治理」)。若是你經過alloc、new或copy來建立一個對象,那麼你必須調用release或autorelease。換句話說,不是你建立的,就不用你去釋放。
例如,你在一個函數中alloc生成了一個對象,且這個對象只在這個函數中被使用,那麼你必須在這個函數中調用release或autorelease。若是你在一個class的某個方法中alloc一個成員對象,且沒有調用autorelease,那麼你須要在這個類的dealloc方法中調用release;若是調用了autorelease,那麼在dealloc方法中什麼都不須要作。
1.2 除了alloc、new或copy以外的方法建立的對象都被聲明瞭autorelease。
1.3 誰retain,誰release。只要你調用了retain,不管這個對象是如何生成的,你都要調用release。有時候你的代碼中明明沒有retain,但是系統會在默認實現中加入retain。不知道爲何蘋果公司的文檔沒有強調這個很是重要的一點,請參考範式2.7和第三章。
2.範式
範式就是模板,就是依葫蘆畫瓢。因爲不一樣人有不一樣的理解和習慣,我總結的範式不必定適合全部人,但我能保證照着這樣作不會出問題。
2.1 建立一個對象。
ClassA *obj1 = [[ClassA alloc] init];
2.2 建立一個autorelease的對象。
ClassA *obj1 = [[[ClassA alloc] init] autorelease];
2.3 Release一個對象後,當即把指針清空。(順便說一句,release一個空指針是合法的,但不會發生任何事情)
[obj1 release]; obj1 = nil;
2.4 指針賦值給另外一個指針。
ClassA *obj2 = obj1; [obj2 retain]; //do something
[obj2 release]; obj2 = nil;
2.5 在一個函數中建立並返回對象,須要把這個對象設置爲autorelease
ClassA *Func1() { ClassA *obj = [[[ClassA alloc]init]autorelease]; return obj; }
2.6 在子類的dealloc方法中調用基類的dealloc方法
-(void) dealloc { … [super dealloc]; }
2.7 在一個class中建立和使用property。
2.7.1 聲明一個成員變量。
ClassB *objB;
2.7.2 聲明property,加上retain參數。
@property (retain) ClassB* objB;
2.7.3 定義property。(property的默認實現請看第三章)
@synthesize objB;
2.7.4 除了dealloc方法之外,始終用.操做符的方式來調用property。
self.objB 或者objA.objB
2.7.5 在dealloc方法中release這個成員變量。
[objB release];
示例代碼以下(詳細代碼請參考附件中的memman-property.m,你須要特別留意對象是在什麼時候被銷燬的。):
@interface ClassA : NSObject { ClassB* objB; } @property (retain) ClassB* objB; @end
@implementation ClassA @synthesize objB; -(void) dealloc { [objB release]; [super dealloc]; } @end
2.7.6 給這個property賦值時,有手動release和autorelease兩種方式。
void funcNoAutorelease() { ClassB *objB1 = [[ClassB alloc]init]; ClassA *objA = [[ClassA alloc]init]; objA.objB = objB1; [objB1 release]; [objA release]; } void funcAutorelease() { ClassB *objB1 = [[[ClassB alloc]init] autorelease]; ClassA *objA = [[[ClassA alloc]init] autorelease]; objA.objB = objB1; }
三 @property (retain)和@synthesize的默認實現
在這裏解釋一下@property (retain) ClassB* objB;和@synthesize objB;背後到底發生了什麼(retain property的默認實現)。property其實是getter和setter,針對有retain參數的property,背後的實現以下(請參考附件中的memman-getter-setter.m,你會發現,結果和memman-property.m同樣):
@interface ClassA : NSObject { ClassB *objB; } -(ClassB *) getObjB; -(void) setObjB:(ClassB *) value; @end @implementation ClassA -(ClassB*) getObjB { return objB; } -(void) setObjB:(ClassB*) value { if (objB != value) { [objB release]; objB = [value retain]; } }
在setObjB中,若是新設定的值和原值不一樣的話,必需要把原值對象release一次,這樣才能保證retain count是正確的。
因爲咱們在class內部retain了一次(雖然是默認實現的),因此咱們要在dealloc方法中release這個成員變量。
-(void) dealloc { [objB release]; [super dealloc]; }
四 系統自動建立新的autorelease pool
在生成新的Run Loop的時候,系統會自動建立新的autorelease pool(很是感謝網友hhyytt和neogui的提醒)。注意,此處不一樣於xcode在新建項目時自動生成的代碼中加入的autorelease pool,xcode生成的代碼能夠被刪除,但系統自動建立的新的autorelease pool是沒法刪除的(對於無Garbage Collection的環境來講)。Objective-C沒有給出實現代碼,官方文檔也沒有說明,但咱們能夠經過小程序來證實。
在這個小程序中,咱們先生成了一個autorelease pool,而後生成一個autorelease的ClassA的實例,再在一個新的run loop中生成一個autorelease的ClassB的對象(注意,咱們並無手動在新run loop中生成autorelease pool)。精簡的示例代碼以下,詳細代碼請見附件中的memman-run-loop-with-pool.m。
int main(int argc, char**argv) { NSLog(@"create an autorelasePool\n"); NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; NSLog(@"create an instance of ClassA and autorelease\n"); ClassA *obj1 = [[[ClassA alloc] init] autorelease]; NSDate *now = [[NSDate alloc] init]; NSTimer *timer = [[NSTimer alloc] initWithFireDate:now interval:0.0 target:obj1 selector:@selector(createClassB) userInfo:nil repeats:NO]; NSRunLoop *runLoop = [NSRunLoop currentRunLoop]; [runLoop addTimer:timer forMode:NSDefaultRunLoopMode]; [timer release]; [now release]; [runLoop run]; //在新loop中調用一函數,生成ClassB的autorelease實例 NSLog(@"releasing autorelasePool\n"); [pool release]; NSLog(@"autorelasePool is released\n"); return 0; }
輸出以下:
create an autorelasePool create an instance of ClassA and autorelease create an instance of ClassB and autorelease ClassB destroyed releasing autorelasePool ClassA destroyed autorelasePool is released
注意在咱們銷燬autorelease pool以前,ClassB的autorelease實例就已經被銷燬了。
有人可能會說,這並不能說明新的run loop自動生成了一個新的autorelease pool,說不定還只是用了老的autorelease pool,只不事後來drain了一次而已。咱們能夠在main函數中不生成autorelease pool。精簡的示例代碼以下,詳細代碼請見附件中的memman-run-loop-without-pool.m。
int main(int argc, char**argv) { NSLog(@"No autorelasePool created\n"); NSLog(@"create an instance of ClassA\n"); ClassA *obj1 = [[ClassA alloc] init]; NSDate *now = [[NSDate alloc] init]; NSTimer *timer = [[NSTimer alloc] initWithFireDate:now interval:0.0 target:obj1 selector:@selector(createClassB) userInfo:nil repeats:NO]; NSRunLoop *runLoop = [NSRunLoop currentRunLoop]; [runLoop addTimer:timer forMode:NSDefaultRunLoopMode]; [timer release]; [now release]; [runLoop run]; //在新loop中調用一函數,生成ClassB的autorelease實例 NSLog(@"Manually release the instance of ClassA\n"); [obj1 release]; return 0; }
輸出以下:
No autorelasePool created
create an instance of ClassA
create an instance of ClassB and autorelease
ClassB destroyed
Manually release the instance of ClassA
ClassA destroyed
咱們能夠看出來,咱們並無建立任何autorelease pool,但是ClassB的實例依然被自動銷燬了,這說明新的run loop自動建立了一個autorelease pool,這個pool在新的run loop結束的時候會銷燬本身(並自動release所包含的對象)。
補充說明
在研究retain count的時候,我不建議用NSString。由於在下面的語句中,
NSString *str1 = @」constant string」;
str1的retain count是個很大的數字。Objective-C對常量字符串作了特殊處理。
固然,若是你這樣建立NSString,獲得的retain count依然爲1
NSString *str2 = [NSString stringWithFormat:@」123」];
擴展閱讀:
Objective-C 2.0屬性(Property) retain和copy的區別