Obj-C中的內存管理不同於C語言式的完全手動管理方式,malloc和free操控內存,也不同於GC(Gabage Collection)語言(例如java&ruby等)的自動回收方式,在Obj-C中的內存管理方式採用的是比較折中的方式,也就是手動和半自動結合,採用自動對象釋放池進行自動管理或者採用引用計數值進行手動管理。
1. 引用計數值
至於爲什麼要進行內存管理,這裏我想大家應該基本瞭解了,即使我這種不太瞭解底層的碼畜都知道一些,那麼內存管理就是CPU內存資源的分配和回收,良好的內存管理機制在於儘量控制CPU內存資源的浪費。
Obj-C中語言中引用計數值的管理方式,對象每次alloc1次計數值爲1同時獲得內存,每retain 1次計數值加1,每release 1次計數值減1,直到計數值爲0的時候,對象就會被銷燬,主要的操控接口如下:
Alloc、allocWithZone、new(並且初始化)——爲對象分配內存,計數值+1,返回實例
Release——計數值-1
Retain——計數值+1
Copy、mutableCopy——複製一個實例,計數值爲1,返回實例,所得到的對象是一個全新的對象
Autorelease在當前的上下文的autoreleasePool棧頂添加對象,由於它的引入使Obj-C的內存管理方式由全手動上升爲半自動。
那麼我們來看一段代碼實例,演示下內存的計數值管理
- #import <Foundation/NSObject.h>
- #import <Foundation/NSAutoreleasePool.h>
- #import <Foundation/NSString.h>
- #import <Foundation/NSArray.h>
- #import <Foundation/NSValue.h>
- Int main (int argc, char * argv[])
- {
- NSAutoreleasePool* pool=[[NSAutoreleasePool alloc] init];
- NSNumber*myInt=[[NSNumber alloc] initWithInt: 100];//實例化一個NSNumber對象myInt,並且調用alloc方法,其引用計數值加1,爲+1
- NSLog(@”myInt retain count=%lx”, (unsigned long) [myInt retainCount]);
- //輸出myInt的引用計數
- [myInt retain];//myInt對象調用retain方法,引用計數值再+1,爲+2
- NSLog(@”myInt retain count after retain=%lx”, (unsigned long) [myInt retainCount]);
- //輸出myInt的引用計數
- [myInt release];//myInt對象調用release方法,引用計數值-1,爲+1,此時myInt對象仍然存在,並未釋放
- NSLog(@”myInt retain count after release=%lx”, (unsigned long) [myInt retainCount]);
- //輸出myInt的引用計數值
- NSNumber* secant=[myInt copy];//myInt調用copy方法,返回引用計數爲1的實例,此時secInt的引用計數值爲1,myInt的引用計數值不變
- NSLog(@” secant retain count after copy=%lx”, (unsigned long) [secant retainCount]);
- [secInt release];//secInt調用release方法,引用計數-1,爲0,secInt對象被徹底釋放
- [myInt release];//myInt對象調用release方法,應用計數-1,爲0,myInt對象被徹底釋放
- [pool drain];
- return 0;
- }
程序的最終輸出結果如下:
myInt retain count=1
myInt retain count after retaine=2
myInt retain count after release=1
secInt retain count after copy=1
上述這段代碼演示了計數機制是如何工作的,簡單的規則就是,alloc和retain會讓對象引用計數+1,release會讓對象的引用計數-1,而copy對象則是隻會將新的對象的引用計數+1,所以簡單的規避內存泄露的一個方法就是有多少個alloc和retain,就要有多少個相應的release。
自動釋放池
在ObjC中也有一種內存自動釋放的機制叫做“自動引用計數”(或“自動釋放池”),與C#、Java不同的是,這只是一種半自動的機制,有些操作還是需要我們手動設置的。自動內存釋放使用@autoreleasepool關鍵字聲明一個代碼塊,如果一個對象在初始化時調用了autorelase方法,那麼當代碼塊執行完之後,在塊中調用過autorelease方法的對象都會自動調用一次release方法。這樣一來就起到了自動釋放的作用,同時對象的銷燬過程也得到了延遲(統一調用release方法)。看下面的代碼:
Person.h
// // Person.h // MemoryManage // // Created by Kenshin Cui on 14-2-15. // Copyright (c) 2014年 Kenshin Cui. All rights reserved. // #import <Foundation/Foundation.h> @interface Person : NSObject #pragma mark - 屬性 #pragma mark 姓名 @property (nonatomic,copy) NSString *name; #pragma mark - 公共方法 #pragma mark 帶參數的構造函數 -(Person *)initWithName:(NSString *)name; #pragma mark 取得一個對象(靜態方法) +(Person *)personWithName:(NSString *)name; @end
Person.m
// // Person.m // MemoryManage // // Created by Kenshin Cui on 14-2-15. // Copyright (c) 2014年 Kenshin Cui. All rights reserved. // #import "Person.h" @implementation Person #pragma mark - 公共方法 #pragma mark 帶參數的構造函數 -(Person *)initWithName:(NSString *)name{ if(self=[super init]){ self.name=name; } return self; } #pragma mark 取得一個對象(靜態方法) +(Person *)personWithName:(NSString *)name{ Person *p=[[[Person alloc]initWithName:name] autorelease];//注意這裏調用了autorelease return p; } #pragma mark - 覆蓋方法 #pragma mark 重寫dealloc方法 -(void)dealloc{ NSLog(@"Invoke Person(%@) dealloc method.",self.name); [super dealloc]; } @end
main.m
// // main.m // MemoryManage // // Created by Kenshin Cui on 14-2-15. // Copyright (c) 2014年 Kenshin Cui. All rights reserved. // #import <Foundation/Foundation.h> #import "Person.h" int main(int argc, const char * argv[]) { @autoreleasepool { Person *person1=[[Person alloc]init]; [person1 autorelease];//調用了autorelease方法後面就不需要手動調用release方法了 person1.name=@"Kenshin";//由於autorelease是延遲釋放,所以這裏仍然可以使用person1 Person *person2=[[[Person alloc]initWithName:@"Kaoru"] autorelease];//調用了autorelease方法 Person *person3=[Person personWithName:@"rosa"];//內部已經調用了autorelease,所以不需要手動釋放,這也符合內存管理原則,因爲這裏並沒有alloc所以不需要release或者autorelease Person *person4=[Person personWithName:@"jack"]; [person4 retain]; } /*結果: Invoke Person(rosa) dealloc method. Invoke Person(Kaoru) dealloc method. Invoke Person(Kenshin) dealloc method. */ return 0; }
當上面@autoreleaespool代碼塊執行完之後,三個對象都得到了釋放,但是person4並沒有釋放,原因很簡單,由於我們手動retain了一次,當自動釋放池釋放後調用四個對的release方法,當調用完person4的release之後它的引用計數器爲1,所有它並沒有釋放(這是一個反例,會造成內存泄露);autorelase方法將一個對象的內存釋放延遲到了自動釋放池銷燬的時候,因此上面person1,調用完autorelase之後它還存在,因此給name賦值不會有任何問題;在ObjC中通常如果一個靜態方法返回一個對象本身的話,在靜態方法中我們需要調用autorelease方法,因爲按照內存釋放原則,在外部使用時不會進行alloc操作也就不需要再調用release或者autorelase,所以這個操作需要放到靜態方法內部完成。
對於自動內存釋放簡單總結一下:
- autorelease方法不會改變對象的引用計數器,只是將這個對象放到自動釋放池中;
- 自動釋放池實質是當自動釋放池銷燬後調用對象的release方法,不一定就能銷燬對象(例如如果一個對象的引用計數器>1則此時就無法銷燬);
- 由於自動釋放池最後統一銷燬對象,因此如果一個操作比較佔用內存(對象比較多或者對象佔用資源比較多),最好不要放到自動釋放池或者考慮放到多個自動釋放池;
- ObjC中類庫中的靜態方法一般都不需要手動釋放,內部已經調用了autorelease方法;,系統又是什麼時候釋放的呢?在每一個事件週期(event cycle)的開始,系統會自動創建一個自動釋放池; 在每一個事件週期的結尾,系統會自動銷燬這個自動釋放池。一般情況下,你可以理解爲:當你的代碼在持續運行時,自動釋放池是不會被銷燬的,這段時間內你也可以安全地使用自動釋放的對象;當你的代碼運行告一段落,開始等待用戶輸入(或者其它事件)時,自動釋放池就會被釋放掉,池中的對象都會收到一個release消息,有的可能會因此被銷燬。 這是很難確定的時間,如果自動釋放池的銷燬時間過早,那麼程序就很危險,這個恐怕很難滿足程序員的要求吧。 自動釋放池的缺點:它延緩了對象的釋放,在有大量自動釋放的對象時,會佔用大量內存資源。因此,你需要避免將大量對象自動釋放。並且,在以下兩種情況下,你需要手動建立並手動銷燬掉自動釋放池:1.當你在主線程外開啓其它線程時:系統只會在主線程中自動生成並銷燬掉自動釋放池。2.當你在短時間內製造了大量自動釋放對象時:及時地銷燬有助於有效利用iPad上有限地內存資源。
屬性參數
像上面這樣編寫setCar方法的情況是比較多的,那麼如何使用@property進行自動實現呢?答案就是使用屬性參數,例如上面car屬性的setter方法,可以通過@property定義如下:
@property (nonatomic,retain) Car *car;
你會發現此刻我們不必手動實現car的getter、setter方法程序仍然沒有內存泄露。其實大家也應該都已經看到前面Person的name屬性定義的時候我們同樣加上了(nonatomic,copy)參數,這些參數到底是什麼意思呢?