今天看到一篇不錯的文章關(guān)于OC內(nèi)存管理的,轉(zhuǎn)載一下與你共享概述我們知道在程序運行過程中要創(chuàng)建大量的對象氏义,和其他高級語言類似锄列,在ObjC中對象時存儲在堆中的,系統(tǒng)并不會自動釋放堆中的內(nèi)存(注意基本類型是由系統(tǒng)自己管理的觅赊,放在棧上)右蕊。
如果一個對象創(chuàng)建并使用后沒有得到及時釋放那么就會占用大量內(nèi)存。其他高級語言如C#吮螺、Java都是通過垃圾回收來(GC)解決這個問題的饶囚,但在OjbC中并沒有類似的垃圾回收機制,因此它的內(nèi)存管理就需要由開發(fā)人員手動維護(hù)鸠补。
今天將著重介紹ObjC內(nèi)存管理:1引用計數(shù)器2屬性參數(shù)3自動釋放池引用計數(shù)器在Xcode4.2及之后的版本中由于引入了ARC(Automatic Reference Counting)機制,程序編譯時Xcode可以自動給你的代碼添加內(nèi)存釋放代碼萝风,如果編寫手動釋放代碼Xcode會報錯,因此在今天的內(nèi)容中如果你使用的是Xcode4.2之后的版本(相信現(xiàn)在大部分朋友用的版本都比這個要高)紫岩,必須手動關(guān)閉ARC规惰,這樣才有助于你理解ObjC的內(nèi)存回收機制。ObjC中的內(nèi)存管理機制跟C語言中指針的內(nèi)容是同樣重要的泉蝌,要開發(fā)一個程序并不難歇万,但是優(yōu)秀的程序則更測重于內(nèi)存管理,它們往往占用內(nèi)存更少勋陪,運行更加流暢贪磺。雖然在新版Xcode引入了ARC,但是很多時候它并不能完全解決你的問題诅愚。
在Xcode中關(guān)閉ARC:項目屬性—Build Settings--搜索“garbage”找到Objective-C Automatic Reference Counting設(shè)置為No即可寒锚。
內(nèi)存管理原理我們都知道在C#、Java中都有GC在自動管理內(nèi)存违孝,當(dāng)我們實例化一個對象之后通常會有一個變量來引用這個對象(變量中存儲對象地址)刹前,當(dāng)這個引用變量不再使用之后(也就是不再引用這個對象)此時GC就會自動回收這個對象,簡單的說就是:當(dāng)一個對象沒有任何變量引用的時候就會被回收雌桑。
例如下面的C#代碼片段
using System;namespace GC{? ?
?class Program? ? {? ? ? ??
private static void Test()? ? ? ? {? ? ? ? ? ?
?object o=new object();? ? ? ? }? ? ? ??
static void Main(string[] args)? ? ? ? {? ? ? ? ??
? Test();? ? ?
?? }? ? }}
上面是一段C#代碼喇喉,在Test()方法中,通過new Object()創(chuàng)建了一個對象校坑,o是一個對象的引用(存儲了對象的地址)轧飞,它是一個局部變量衅鹿,作用范圍就是Test()方法內(nèi)部撒踪。當(dāng)執(zhí)行完Test()方法之后o就會被釋放过咬,此時由于沒有變量在引用new Object()這個對象,因此GC會自動回收這個對象所占用的空間制妄。但是在ObjC中沒有垃圾回收機制掸绞,那么ObjC中內(nèi)存又是如何管理的呢?其實在ObjC中內(nèi)存的管理是依賴對象引用計數(shù)器來進(jìn)行的:在ObjC中每個對象內(nèi)部都有一個與之對應(yīng)的整數(shù)(retainCount)耕捞,叫“引用計數(shù)器”衔掸,當(dāng)一個對象在創(chuàng)建之后它的引用計數(shù)器為1,當(dāng)調(diào)用這個對象的alloc俺抽、retain敞映、new、copy方法之后引用計數(shù)器自動在原來的基礎(chǔ)上加1(ObjC中調(diào)用一個對象的方法就是給這個對象發(fā)送一個消息)磷斧,當(dāng)調(diào)用這個對象的release方法之后它的引用計數(shù)器減1振愿,如果一個對象的引用計數(shù)器為0,則系統(tǒng)會自動調(diào)用這個對象的dealloc方法來銷毀這個對象弛饭。
下面通過一個簡單的例子看一下引用計數(shù)器的知識:
Person.h////? Person.h//? MemoryManage////? Kenshin Cui on 14-2-15.//? Copyright (c) 2014年 Kenshin Cui. All rights reserved.//#import@interface Person : NSObject#pragma mark - 屬性@property (nonatomic,copy) NSString *name;@property (nonatomic,assign) int age;@endPerson.m////? Person.m//? MemoryManage////? Kenshin Cui on 14-2-15.//? Copyright (c) 2014年 Kenshin Cui. All rights reserved.//#import "Person.h"@implementation Person#pragma mark - 覆蓋方法#pragma mark 重寫dealloc方法冕末,在這個方法中通常進(jìn)行對象釋放操作-(void)dealloc{? ? NSLog(@"Invoke Person's dealloc method.");? ? [super dealloc];//注意最后一定要調(diào)用父類的dealloc方法(兩個目的:一是父類可能有其他引用對象需要釋放;二是:當(dāng)前對象真正的釋放操作是在super的dealloc中完成的)}@endmain.m////? main.m//? MemoryManage////? Created by Kenshin Cui on 14-2-15.//? Copyright (c) 2014年 Kenshin Cui. All rights reserved.//#import#import "Person.h"void Test1(){? ? Person *p=[[Person alloc]init]; //調(diào)用alloc侣颂,引用計數(shù)器+1? ? p.name=@"Kenshin";? ? p.age=28;? ? ? ? NSLog(@"retainCount=%lu",[p retainCount]);? ? //結(jié)果:retainCount=1? ? ? ? [p release];? ? //結(jié)果:Invoke Person's dealloc method.? ? ? ? ? ? ? ? //上面調(diào)用過release方法档桃,p指向的對象就會被銷毀,但是此時變量p中還存放著Person對象的地址憔晒,? ? //如果不設(shè)置p=nil藻肄,則p就是一個野指針,它指向的內(nèi)存已經(jīng)不屬于這個程序拒担,因此是很危險的? ? p=nil;? ? //如果不設(shè)置p=nil嘹屯,此時如果再調(diào)用對象release會報錯,但是如果此時p已經(jīng)是空指針了澎蛛,? ? //則在ObjC中給空指針發(fā)送消息是不會報錯的? ? [p release];}void Test2(){? ? Person *p=[[Person alloc]init];? ? p.name=@"Kenshin";? ? p.age=28;? ? ? ? NSLog(@"retainCount=%lu",[p retainCount]);? ? //結(jié)果:retainCount=1? ? ? ? [p retain];//引用計數(shù)器+1? ? NSLog(@"retainCount=%lu",[p retainCount]);? ? //結(jié)果:retainCount=2? ? ? ? [p release];//調(diào)用1次release引用計數(shù)器-1? ? NSLog(@"retainCount=%lu",[p retainCount]);? ? //結(jié)果:retainCount=1? ? [p release];? ? //結(jié)果:Invoke Person's dealloc method.? ? p=nil;}int main(int argc, const char * argv[]) {? ? @autoreleasepool {? ? ? ? Test1();? ? }? ? return 0;}在上面的代碼中我們可以通過dealloc方法來查看是否一個對象已經(jīng)被回收抚垄,如果沒有被回收則有可能造成內(nèi)存泄露。如果一個對象被釋放之后谋逻,那么最后引用它的變量我們手動設(shè)置為nil呆馁,否則可能造成野指針錯誤,而且需要注意在ObjC中給空對象發(fā)送消息是不會引起錯誤的毁兆。野指針錯誤形式在Xcode中通常表現(xiàn)為:Thread 1:EXC_BAD_ACCESS(code=EXC_I386_GPFLT)錯誤浙滤。因為你訪問了一塊已經(jīng)不屬于你的內(nèi)存。內(nèi)存釋放的原則手動管理內(nèi)存有時候并不容易气堕,因為對象的引用有時候是錯綜復(fù)雜的纺腊,對象之間可能互相交叉引用畔咧,此時需要遵循一個法則:誰創(chuàng)建,誰釋放揖膜。假設(shè)現(xiàn)在有一個人員Person類誓沸,每個Person可能會購買一輛汽車Car,通常情況下購買汽車這個活動我們可能會單獨抽取到一個方法中壹粟,同時買車的過程中我們可能會多看幾輛來最終確定理想的車拜隧,現(xiàn)在我們的代碼如下:Car.h////? Car.h//? MemoryManage////? Created by Kenshin Cui on 14-2-15.//? Copyright (c) 2014年 Kenshin Cui. All rights reserved.//#import@interface Car : NSObject#pragma mark - 屬性#pragma mark 車牌號@property (nonatomic,copy) NSString *no;#pragma mark - 公共方法#pragma mark 運行方法-(void)run;@endCar.m////? Car.m//? MemoryManage////? Created by Kenshin Cui on 14-2-15.//? Copyright (c) 2014年 Kenshin Cui. All rights reserved.//#import "Car.h"@implementation Car#pragma mark - 公共方法#pragma mark 運行方法-(void)run{? ? NSLog(@"Car(%@) run.",self.no);}#pragma mark - 覆蓋方法#pragma mark 重寫dealloc方法-(void)dealloc{? ? ? ? NSLog(@"Invoke Car(%@) dealloc method.",self.no);? ? [super dealloc];}@endPerson.h////? Person.h//? MemoryManage////? Created by Kenshin Cui on 14-2-15.//? Copyright (c) 2014年 Kenshin Cui. All rights reserved.//#import@class Car;@interface Person : NSObject{? ? Car *_car;}#pragma mark - 屬性#pragma mark 姓名@property (nonatomic,copy) NSString *name;#pragma mark - 公共方法#pragma mark Car屬性的set方法-(void)setCar:(Car *)car;#pragma mark? Car屬性的get方法-(Car *)car;@endPerson.m////? Person.m//? MemoryManage////? Created by Kenshin Cui on 14-2-15.//? Copyright (c) 2014年 Kenshin Cui. All rights reserved.//#import "Person.h"#import "Car.h"@implementation Person#pragma mark - 公共方法#pragma mark Car屬性的set方法-(void)setCar:(Car *)car{? ? if (_car!=car) { //首先判斷要賦值的變量和當(dāng)前成員變量是不是同一個變量? ? ? ? [_car release]; //釋放之前的對象? ? ? ? _car=[car retain];//賦值時重新retain? ? }}#pragma mark? Car屬性的get方法-(Car *)car{? ? return _car;}#pragma mark - 覆蓋方法#pragma mark 重寫dealloc方法-(void)dealloc{? ? NSLog(@"Invoke Person(%@) dealloc method.",self.name);? ? [_car release];//在此釋放對象慈迈,即使沒有賦值過由于空指針也不會出錯? ? [super dealloc];}@endmain.m////? main.m//? MemoryManage////? Created by Kenshin Cui on 14-2-15.//? Copyright (c) 2014年 Kenshin Cui. All rights reserved.//#import#import "Person.h"#import "Car.h"void getCar(Person *p){? ? Car *car1=[[Car alloc]init];? ? car1.no=@"888888";? ? ? ? p.car=car1;? ? ? ? NSLog(@"retainCount(p)=%lu",[p retainCount]);? ? ? ? Car *car2=[[Car alloc]init];? ? car2.no=@"666666";? ? ? ? [car1 release];? ? car1=nil;? ? ? ? [car2 release];? ? car2=nil;}int main(int argc, const char * argv[]) {? ? @autoreleasepool {? ? ? ? Person *p=[[Person alloc]init];? ? ? ? p.name=@"Kenshin";? ? ? ? ? ? ? ? getCar(p);? ? ? ? ? ? ? ? [p.car run];? ? ? ? ? ? ? ? [p release];? ? ? ? ? ? ? ? p=nil;? ? ? ? ? ? }? ? return 0;}程序運行結(jié)果:從運行結(jié)果來看創(chuàng)建的三個對象p跳仿、car1迁酸、car2都被回收了产上,而且[p.car run]也能順利運行面哼,已經(jīng)達(dá)到了我們的需求牙寞。但是這里需要重點解釋一下setCar方法的實現(xiàn),setCar方法中為什么沒有寫成如下形式:-(void)setCar:(Car *)car{? ? _car=car;}前面在我們說到屬性的定義時不是都采用的這種方式嗎快耿?根據(jù)前面說到的內(nèi)存釋放原則糯彬,getCar方法完全符合盏袄,在這個方法中定義的兩個對象car1忿峻、car2也都是在這個方法中釋放的,包括main函數(shù)中的p對象也是在main函數(shù)中定義和釋放的貌矿。但是如果發(fā)現(xiàn)調(diào)用完getCar方法之后緊接著調(diào)用了汽車的run方法炭菌,當(dāng)然這在程序設(shè)計和開發(fā)過程中應(yīng)該是再普通不過的設(shè)計了。如果setCar寫成“_car=car”的形式逛漫,當(dāng)調(diào)用完getCar方法后黑低,人員的car屬性被釋放了,此時調(diào)用run方法是會報錯的(大家自己可以試試)酌毡。但是如下的方式卻不會有問題:-(void)setCar:(Car *)car{? ? if (_car!=car) { //首先判斷要賦值的變量和當(dāng)前成員變量是不是同一個變量? ? ? ? [_car release]; //釋放之前的對象? ? ? ? _car=[car retain];//賦值時重新retain? ? }}因為在這個方法中我們通過[car retain]保證每次屬性賦值的時候?qū)ο笠糜嫈?shù)器+1,這樣一來調(diào)用過getCar方法可以保證人員的car屬性不會被釋放克握,其次為了保證上一次的賦值對象(car1)能夠正常釋放,我們在賦新值之前對原有的值進(jìn)行release操作枷踏。最后在Person的dealloc方法中對_car進(jìn)行一次release操作(因為setCar中做了一次retain操作)保證_car能正称邪担回收。屬性參數(shù)像上面這樣編寫setCar方法的情況是比較多的旭蠕,那么如何使用@property進(jìn)行自動實現(xiàn)呢停团?答案就是使用屬性參數(shù),例如上面car屬性的setter方法掏熬,可以通過@property定義如下:@property (nonatomic,retain) Car *car;你會發(fā)現(xiàn)此刻我們不必手動實現(xiàn)car的getter佑稠、setter方法程序仍然沒有內(nèi)存泄露。其實大家也應(yīng)該都已經(jīng)看到前面Person的name屬性定義的時候我們同樣加上了(nonatomic,copy)參數(shù)旗芬,這些參數(shù)到底是什么意思呢舌胶?@property的參數(shù)分為三類,也就是說參數(shù)最多可以有三個疮丛,中間用逗號分隔幔嫂,每類參數(shù)可以從上表三類參數(shù)中人選一個辆它。如果不進(jìn)行設(shè)置或者只設(shè)置其中一類參數(shù),程序會使用三類中的各個默認(rèn)參數(shù)履恩,默認(rèn)參數(shù):(atomic,readwrite,assign)一般情況下如果在多線程開發(fā)中一個屬性可能會被兩個及兩個以上的線程同時訪問锰茉,此時可以考慮atomic屬性,否則建議使用nonatomic似袁,不加鎖洞辣,效率較高;readwirte方法會生成getter昙衅、setter兩個方法,如果使用readonly則只生成getter方法定鸟;關(guān)于set方法處理需要特別說明而涉,假設(shè)我們定義一個屬性a,這里列出三種方式的生成代碼:assign联予,用于基本數(shù)據(jù)類型-(void)setA:(int)a{? ? _a=a;}retain啼县,通常用于非字符串對象-(void)setA:(Car *)a{? ? if(_a!=a){? ? ? ? [_a release];? ? ? ? _a=[a retain];? ? }}copy,通常用于字符串對象沸久、block季眷、NSArray、NSDictionary-(void)setA:(NSString *)a{? ? if(_a!=a){? ? ? ? [_a release];? ? ? ? _a=[a copy];? ? }}備注:本文基于MRC進(jìn)行介紹卷胯,ARC下的情況不同子刮,請參閱其他文章,例如ARC下基本數(shù)據(jù)類型默認(rèn)的屬性參數(shù)為(atomic,readwrite,assign)窑睁,對象類型默認(rèn)的屬性參數(shù)為(atomic,readwrite,strong)自動釋放池在ObjC中也有一種內(nèi)存自動釋放的機制叫做“自動引用計數(shù)”(或“自動釋放池”)挺峡,與C#、Java不同的是担钮,這只是一種半自動的機制橱赠,有些操作還是需要我們手動設(shè)置的。自動內(nèi)存釋放使用@autoreleasepool關(guān)鍵字聲明一個代碼塊箫津,如果一個對象在初始化時調(diào)用了autorelase方法狭姨,那么當(dāng)代碼塊執(zhí)行完之后,在塊中調(diào)用過autorelease方法的對象都會自動調(diào)用一次release方法苏遥。這樣一來就起到了自動釋放的作用饼拍,同時對象的銷毀過程也得到了延遲(統(tǒng)一調(diào)用release方法)∨郏看下面的代碼:Person.h////? Person.h//? MemoryManage////? Created by Kenshin Cui on 14-2-15.//? Copyright (c) 2014年 Kenshin Cui. All rights reserved.//#import@interface Person : NSObject#pragma mark - 屬性#pragma mark 姓名@property (nonatomic,copy) NSString *name;#pragma mark - 公共方法#pragma mark 帶參數(shù)的構(gòu)造函數(shù)-(Person *)initWithName:(NSString *)name;#pragma mark 取得一個對象(靜態(tài)方法)+(Person *)personWithName:(NSString *)name;@endPerson.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 帶參數(shù)的構(gòu)造函數(shù)-(Person *)initWithName:(NSString *)name{? ? if(self=[super init]){? ? ? ? self.name=name;? ? }? ? return self;}#pragma mark 取得一個對象(靜態(tài)方法)+(Person *)personWithName:(NSString *)name{? ? Person *p=[[[Person alloc]initWithName:name] autorelease];//注意這里調(diào)用了autorelease? ? return p;}#pragma mark - 覆蓋方法#pragma mark 重寫dealloc方法-(void)dealloc{? ? NSLog(@"Invoke Person(%@) dealloc method.",self.name);? ? [super dealloc];}@endmain.m////? main.m//? MemoryManage////? Created by Kenshin Cui on 14-2-15.//? Copyright (c) 2014年 Kenshin Cui. All rights reserved.//#import#import "Person.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *person1=[[Person alloc]init];
[person1 autorelease];//調(diào)用了autorelease方法后面就不需要手動調(diào)用release方法了
person1.name=@"Kenshin";//由于autorelease是延遲釋放惕耕,所以這里仍然可以使用person1
Person *person2=[[[Person alloc]initWithName:@"Kaoru"] autorelease];//調(diào)用了autorelease方法
Person *person3=[Person personWithName:@"rosa"];//內(nèi)部已經(jīng)調(diào)用了autorelease,所以不需要手動釋放诫肠,這也符合內(nèi)存管理原則司澎,因為這里并沒有alloc所以不需要release或者autorelease
Person *person4=[Person personWithName:@"jack"];
[person4 retain];
}
/*結(jié)果:
Invoke Person(rosa) dealloc method.
Invoke Person(Kaoru) dealloc method.
Invoke Person(Kenshin) dealloc method.
*/
return 0;
}
當(dāng)上面@autoreleaespool代碼塊執(zhí)行完之后欺缘,三個對象都得到了釋放,但是person4并沒有釋放挤安,原因很簡單谚殊,由于我們手動retain了一次,當(dāng)自動釋放池釋放后調(diào)用四個對的release方法蛤铜,當(dāng)調(diào)用完person4的release之后它的引用計數(shù)器為1嫩絮,所有它并沒有釋放(這是一個反例,會造成內(nèi)存泄露)围肥;autorelase方法將一個對象的內(nèi)存釋放延遲到了自動釋放池銷毀的時候剿干,因此上面person1,調(diào)用完autorelase之后它還存在穆刻,因此給name賦值不會有任何問題置尔;在ObjC中通常如果一個靜態(tài)方法返回一個對象本身的話,在靜態(tài)方法中我們需要調(diào)用autorelease方法氢伟,因為按照內(nèi)存釋放原則榜轿,在外部使用時不會進(jìn)行alloc操作也就不需要再調(diào)用release或者autorelase,所以這個操作需要放到靜態(tài)方法內(nèi)部完成朵锣。
對于自動內(nèi)存釋放簡單總結(jié)一下:
1 autorelease方法不會改變對象的引用計數(shù)器谬盐,只是將這個對象放到自動釋放池中;
2 自動釋放池實質(zhì)是當(dāng)自動釋放池銷毀后調(diào)用對象的release方法诚些,不一定就能銷毀對象(例如如果一個對象的引用計數(shù)器>1則此時就無法銷毀)飞傀;
3 由于自動釋放池最后統(tǒng)一銷毀對象,因此如果一個操作比較占用內(nèi)存(對象比較多或者對象占用資源比較多)泣刹,最好不要放到自動釋放池或者考慮放到多個自動釋放池助析;
4 ObjC中類庫中的靜態(tài)方法一般都不需要手動釋放,內(nèi)部已經(jīng)調(diào)用了autorelease方法椅您;
轉(zhuǎn)載來自崔江濤(KenshinCui)
相關(guān)鏈接:http://www.cnblogs.com/kenshincui/p/3870325.html