你好,歡迎來到IOS教程網

 Ios教程網 >> IOS訊息 >> 關於IOS >> oc 內存管理

oc 內存管理

編輯:關於IOS

iPhone/Mac Objective-C 內存管理教程和原理剖析

前言 初學 objectice-C 的朋友都有一個困惑,總覺得對 objective-C 的內存管理 機制琢磨不透,程序經常內存洩漏或莫名其妙的崩潰。我在這裡總結了自己對 o bjective-C 內存管理機制的研究成果和經驗,寫了這麼一個由淺入深的教程。 希望對大家有所幫助,也歡迎大家一起探討。 此文涉及的內存管理是針對於繼承於 NSObject 的 Class。 一 基本原理 Objective-C 的內存管理機制與.Net/Java 那種全自動的垃圾回收機制是不 同的,它本質上還是 C 語言中的手動管理方式,只不過稍微加了一些自動方法。 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,d ealloc 自動被調用,對象被銷毀 我們回頭看看剛剛那個無效指針的問題,把 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 不會自動增加,需要手動 retai n。 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 coun t = 1 但無需調用 release 5.2 對於存在指針賦值的情況,代碼與前面類似。 ClassA *obj1 = [[[ClassA alloc] init] autorelease]; //retain coun t= 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 不是天生的,需要手動創立。只不過在新建一個 ip hone 項目時,xcode 會自動幫你寫好。autorelease pool 的真名是 NSAutorele asePool。     NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; 6.2 NSAutoreleasePool 內部包含一個數組(NSMutableArray),用來保存聲 明為 autorelease 的所有對象。如果一個對象聲明為 autorelease,系統所做的 工作就是把這個對象加入到這個數組中去。 ClassA *obj1 = [[[ClassA alloc] init] autorelease]; //retain coun t = 1,把此對象加入 autorelease pool 中 6.3 NSAutoreleasePool 自身在銷毀的時候,會遍歷一遍這個數組,releas e 數組中的每個成員。如果此時數組中成員的 retain count 為 1,那麼 release 之後,retain count 為 0,對象正式被銷毀。如果此時數組中成員的 retain co unt 大於 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"];//產生的對象是 autorele ase 的。     }     [pool release];     return (0);     } // main (可以參考附件中的示例程序 memman-many-objs-one-pool.m,運行時通過 監控工具可以發現使用的內存在急劇增加,直到 pool 銷毀時才被釋放)你需要考 慮下一條。 7 Objective-C 程序中可以嵌套創建多個 autorelease pool。在需要大量創 建局部變量的時候,可以創建內嵌的 autorelease pool 來及時釋放內存。(感謝 網友 hhyytt 和 neogui 的提醒,某些情況下,系統會自動創建 autorelease poo l, 請參見第四章) int main (int argc, const char *argv[])     {     NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];     int i, j;     for (i = 0; i < 100; i  )     {     NSAutoreleasePool *loopPool = [[NSAutoreleasePool alloc] init];     for (j = 0; j < 100000; j  ) [NSString stringWithFormat:@"1234567890"];//產生的對象是 autorele ase 的。     [loopPool release];     }     [pool release];     return (0); } // main (可以參考附件中的示例程序 memman-many-objs-many-pools.m,占用內存 的變化極小) 二 口訣與范式 1 口訣。 1.1 誰創建,誰釋放(類似於“誰污染,誰治理”)。如果你通過 alloc、ne w 或 copy 來創建一個對象,那麼你必須調用 release 或 autorelease。換句話說, 不是你創建的,就不用你去釋放。 例如,你在一個函數中 alloc 生成了一個對象,且這個對象只在這個函數中 被使用,那麼你必須在這個函數中調用 release 或 autorelease。如果你在一個 class 的某個方法中 alloc 一個成員對象,且沒有調用 autorelease,那麼你需 要在這個類的 dealloc 方法中調用 release;如果調用了 autorelease,那麼在 d ealloc 方法中什麼都不需要做。 1.2 除了 alloc、new 或 copy 之外的方法創建的對象都被聲明了 autorelea se。 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 實際上是 gette r 和 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 中,如果新設定的值和原值不同的話,必須要把原值對象 relea se 一次,這樣才能保證 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 Collect ion 的環境來說)。Objective-C 沒有給出實現代碼,官方文檔也沒有說明,但我 們可以通過小程序來證明。 在這個小程序中,我們先生成了一個 autorelease pool,然後生成一個 au torelease 的 ClassA 的實例,再在一個新的 run loop 中生成一個 autorelease 的 ClassB 的對象(注意,我們並沒有手動在新 run loop 中生成 autorelease po ol)。精簡的示例代碼如下,詳細代碼請見附件中的 memman-run-loop-with-poo l.m。     int main(int argc, char**argv)     {     NSLog(@"create an autorelasePool");     NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];     NSLog(@"create an instance of ClassA and autorelease ");     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 ");     [pool release];     NSLog(@"autorelasePool is released ");     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 
  1. 上一頁:
  2. 下一頁:
蘋果刷機越獄教程| IOS教程問題解答| IOS技巧綜合| IOS7技巧| IOS8教程
Copyright © Ios教程網 All Rights Reserved