導(dǎo)讀
- 一、為什么要進(jìn)行內(nèi)存管理
- 二、內(nèi)存管理機(jī)制
- 三、內(nèi)存管理原則
- 四、MRC手動內(nèi)存管理
- 五、ARC自動內(nèi)存管理
- 六、Autorelease自動釋放池
- 參考:http://m.itdecent.cn/p/7903c8283e26
一、 為什么要進(jìn)行內(nèi)存管理
- 移動設(shè)備分配給每個App的內(nèi)存有限,App運行中會創(chuàng)建大量對象, OC對象存儲在堆中,系統(tǒng)不會自動釋放堆中的內(nèi)存,對象沒有及時釋放,就會占用大量內(nèi)存,系統(tǒng)會發(fā)出內(nèi)存警告,對應(yīng)用運行造成影響。
- 相關(guān)概念:
內(nèi)存泄露:代碼塊結(jié)束時其內(nèi)部的所有局部變量會被回收,指向?qū)ο蟮闹羔樢脖换厥?,如果對象沒有指針指向,但依然存在于內(nèi)存中,造成內(nèi)存泄露。
野指針錯誤:訪問了一塊壞的內(nèi)存(已經(jīng)被回收的,不可用的內(nèi)存)。
僵尸對象:所占內(nèi)存已經(jīng)被回收的對象,僵尸對象不能再被使用。(打開僵尸對象檢測)
空指針:沒有指向任何東西的指針(存儲的東西是0、null、 nil),給空指針發(fā)送消息不會報錯。
二、內(nèi)存管理機(jī)制
- 在C#中都有GC在自動管理內(nèi)存,但是在OC中沒有垃圾回收機(jī)制,OC提供了一套機(jī)制來管理內(nèi)存,即“引用計數(shù)”(retain counting):
- 每一個對象都有一個引用計數(shù)(retain count),對象被創(chuàng)建的時候,引用計數(shù)的值是1 當(dāng)調(diào)用對象的alloc、retain、new、copy方法之后引用計數(shù)器值自動在原來的基礎(chǔ)上加1,當(dāng)調(diào)用對象的release方法之后引用計數(shù)器值減1,調(diào)用autorelease 使對象的引用計數(shù)在未來的某個時候減1, 當(dāng)引用計數(shù)值是0的時候,對象將被銷毀。
- 在每個OC對象內(nèi)部,都專門有4個字節(jié)的存儲空間來存儲引用計數(shù)器。
內(nèi)存管理范圍:任何繼承NSObject的對象;OC中的基本類型存儲在棧上,由系統(tǒng)進(jìn)行管理。
內(nèi)存管理方法有:MRC(手動引用計數(shù))、ARC(自動引用計數(shù))、Autorelease(自動釋放池)。
三、內(nèi)存管理原則
- 誰創(chuàng)建, 誰release。
如果你通過alloc、new、copy來創(chuàng)建了一個對象,那么你就必須調(diào)用release或者autorelease方法。不是你創(chuàng)建的就不用你去負(fù)責(zé)。 - 誰retain, 誰release。
只要你調(diào)用了retain,無論這個對象時如何生成的,你都要調(diào)用release。 - 除了alloc、new或copy之外的方法創(chuàng)建的對象在內(nèi)部都被聲明了autorelease,如[UIButton buttonWithType:]
- 在一定的代碼段內(nèi),對同一個對象所做的copy、alloc和retain的操作次數(shù)應(yīng)當(dāng)與release和 autorelease操作的次數(shù)相等。
Apple官網(wǎng)內(nèi)存管理三定律
1. 一個對象可以有一個或多個擁有者
2. 當(dāng)對象一個擁有者都沒有時,就會被回收
3. 一個對象如果想保留不被回收,必須具有擁有者
四、MRC手動引用計數(shù)
- 在引入ARC(Automatic Reference Counting)自動引用計數(shù)機(jī)制之前,OC的內(nèi)存管理需要由開發(fā)人員手動維護(hù)即MRC。
- 在Xcode4.2之后的版本中??引入了ARC,程序編譯時,Xcode可以自動為你的代碼添加內(nèi)存釋放代碼,如果編寫手動釋放代碼Xcode會報錯,因此如果使用的Xcode4.2之后的版本,必須手動關(guān)閉ARC,這樣才有助于我們理解OC的內(nèi)存管理機(jī)制。
-
為了理解OC的內(nèi)存管理機(jī)制,需要在Xcode中關(guān)閉ARC:項目屬性—Build Settings--搜索“garbage”找到Objective-C Automatic Reference Counting設(shè)置為No即可。
(一) Set方法的代碼規(guī)范
- 基本數(shù)據(jù)類型:直接復(fù)制
-(void)setAge:(int)age
{
_age=age;
} - OC對象類型
-(void)setCar:(Car *)car
{
//先判斷是不是新傳進(jìn)來的對象
If(car!=_car)
{
//對舊對象做一次release
[_car release];//若沒有舊對象,則沒有影響
//對新對象做一次retain
_car=[car retain];
}
}
(二) dealloc方法的代碼規(guī)范
- 一定要[super dealloc],而且要放到最后
- 對self(當(dāng)前)所擁有的的其他對象做一次release操作
-(void)dealloc
{
[_car release];
[super dealloc];
}
(三) 實例代碼規(guī)范
- WZKPerson.h
#import <Foundation/Foundation.h>
@interface WZKPerson : NSObject
@property(nonatomic,copy)NSString *name;
@property(nonatomic,assign)NSInteger age;
@end
WZKPerson.m
#import "WZKPerson.h"
@implementation WZKPerson
-(void)dealloc
{
self.name=nil;
/*最后一定要調(diào)用父類的dealloc方法;
目的:一是父類可能有其他引用對象需要釋放;二是當(dāng)前對象真正的釋放操作是在super的dealloc中完成的;
*/
[super dealloc];
}
@end-
main.m(部分代碼)
//調(diào)用alloc,引用計數(shù)+1
WZKPerson *personTest=[[WZKPerson alloc] init];
personTest.name=@"test";
personTest.age=30;//輸出personTest對象的引用計數(shù) NSLog(@"personTest的引用計數(shù):%lu",[personTest retainCount]); //輸出結(jié)果:personTest的引用計數(shù):1 //執(zhí)行personTest的dealloc方法 //調(diào)用過release方法之后,personTest指向的對象就會被銷毀,但是此時變量personTest中還存放著WZKPerson對象的地址 [personTest release]; //如果不設(shè)置personTest=nil,則personTest就是一個野指針,它指向的內(nèi)存不屬于這個程序,非常危險 personTest=nil; //如果不設(shè)置personTest=nil,此時再調(diào)用personTest的release方法會報錯 //如果設(shè)置了personTest=nil,此時personTest已經(jīng)是空指針了,則oc中給空指針發(fā)送消息是不會報錯的 [personTest release]; WZKPerson *personTest2=[[WZKPerson alloc] init]; personTest2.name=@"test2"; personTest2.age=30; //輸出結(jié)果:personTest的引用計數(shù):1 NSLog(@"personTest2的引用計數(shù):%lu",[personTest2 retainCount]); //引用計數(shù)+1 [personTest2 retain]; //輸出結(jié)果:personTest的引用計數(shù):2 NSLog(@"personTest2的引用計數(shù):%lu",[personTest2 retainCount]); //引用計數(shù)-1 [personTest2 release]; //輸出結(jié)果:personTest的引用計數(shù):1 NSLog(@"personTest2的引用計數(shù):%lu",[personTest2 retainCount]); //執(zhí)行personTest2的dealloc方法 [personTest2 release]; personTest2=nil; 在上述代碼中,可以通過dealloc方法來查看是否一個對象已經(jīng)被回收,如果沒有回收,則有可能造成內(nèi)存泄漏。
如果一個對象被釋放后,那么最后引用它的變量需要手動設(shè)置為nil,否則可能造成野指針錯誤。
五、ARC內(nèi)存管理機(jī)制
(一) ARC的判斷準(zhǔn)則
- 只要沒有強指針指向,對象就會被釋放。
(二) 指針分類
- 強指針:默認(rèn)的情況下,所有的指針都是強指針,關(guān)鍵字strong
- 弱指針:_ _weak關(guān)鍵字修飾的指針
- 聲明一個弱指針如下:
_ _weak Person *p;
//ARC中,只要弱指針指向的對象不在了,就直接把弱指針做清空操作。
_ _weak Person *p=[[Person alloc] init];
//不合理,對象一創(chuàng)建出來就被釋放掉,對象釋放掉后,ARC把指針自動清零。 - ARC中在property處不再使用retain,而是使用strong,在dealloc中不需要再[super dealloc]
@property(nonatomic,strong)Dog *dog;
// 意味著生成的成員變量dog是一個強指針,相當(dāng)于以前的retain。
//如果換成是弱指針,則換成weak,不需要加 _。
(三) ARC的特點總結(jié)
- 不允許調(diào)用release、retain、retainCount
- 允許重寫dealloc,但是不允許調(diào)用[super dealloc]
- @property的參數(shù):
Strong:相當(dāng)于原來的retain(適用于OC對象類型),成員變量是強指針
Weak:相當(dāng)于原來的assign(適用于oc對象類型),成員變量是弱指針
Assign:適用于非OC對象類型(基礎(chǔ)類型)
(四) 補充
- 讓程序兼容ARC和非ARC部分。轉(zhuǎn)變?yōu)榉茿RC -fno-objc-arc 轉(zhuǎn)變?yōu)锳RC的, -f-objc-arc 。
-
ARC也需要考慮循環(huán)引用問題:一端使用retain,另一端使用assign。
- 提示:字符串是特殊的對象,但不需要使用release手動釋放,這種字符串對象默認(rèn)就是autorelease的,不用額外的去管內(nèi)存。
六、 Autorelease自動釋放池
- 在OC中存在著一種內(nèi)存自動釋放機(jī)制叫做自動釋放池(或自動引用計數(shù)),但是與C#不同的是,這僅僅是一種半自動的機(jī)制,有些操作還是需要進(jìn)行手動設(shè)置。
(一) 下面通過代碼來了解一下自動釋放池
- WZKPerson.h
//構(gòu)造函數(shù)
-(WZKPerson *)initWithName:(NSString *)name age:(NSInteger)age;
//獲取對象的類方法
+(WZKPerson *)personWithName:(NSString *)name;
-
WZKPerson.m
-(WZKPerson *)initWithName:(NSString *)name age:(NSInteger)age
{
self=[super init];
if (self) {
_name=[name copy];
_age=age;
}
return self;
}+(WZKPerson *)personWithName:(NSString *)name { //這里調(diào)用了autorelease //OC類庫中的類方法一般都不需要手動釋放,內(nèi)部已經(jīng)調(diào)用了autorelease方法; WZKPerson *person=[[[WZKPerson alloc] init] autorelease]; return person; } -
main.m(部分代碼)
int main(int argc, const char * argv[]) {
@autoreleasepool {
WZKPerson *person1=[[WZKPerson alloc] init];
//調(diào)用autorelease方法,后面就不需要手動調(diào)用release方法了
[person1 autorelease];
//由于autorelease是延遲釋放(延遲到自動釋放池銷毀),//所以這里仍然可以使用person1對象 person1.name=@"Kevin"; //調(diào)用autorelease方法 WZKPerson *person2=[[[WZKPerson alloc] initWithName:@"Kevin" age:27] autorelease]; //內(nèi)部已經(jīng)調(diào)用了autorelease,所以不需要手動釋放 //另外由于內(nèi)存管理原則,在外部不使用alloc、new、copy操作, //就不需要調(diào)用release或autorelease,所以這個操作是放到類方法內(nèi)部進(jìn)行完成 WZKPerson *person3=[WZKPerson personWithName:@"Kevin"]; } return 0; }
(二) 自動內(nèi)存釋放總結(jié)
1. 基本用法
- 自動內(nèi)存釋放使用@autoreleasepool關(guān)鍵字聲明一個代碼塊,如果一個對象在初始化時調(diào)用了autorelease方法,會將這個對象放到位于棧頂?shù)尼尫懦刂小?/li>
- 當(dāng)代碼塊執(zhí)行完之后即當(dāng)自動釋放池被銷毀時,在塊中調(diào)用過autorelease方法的對象都會自動調(diào)用一次release方法, 但是不一定能夠銷毀對象,例如:當(dāng)對象引用計數(shù)器值大于1時,該對象就無法銷毀。
- OC中類庫的類方法一般都不需要手動釋放,因為內(nèi)部已經(jīng)調(diào)用了autorelease方法。
2. 好處
- 不需要再關(guān)心對象釋放的時間
- 不需要再關(guān)心什么時候調(diào)用release
3. 使用注意
- 由于自動釋放池最后統(tǒng)一銷毀對象,因此如果一個操作比較占用內(nèi)存,最好不要放到自動釋放池或者放到多個自動釋放池;應(yīng)該使用release來精確控制
- 占用內(nèi)存較小的對象使用autorelease,沒有太大的影響。
- autorelease方法不會改變對象的引用計數(shù)器(銷毀時影響),只是將這個對象放到自動釋放池中;
- 系統(tǒng)自帶的方法中,如果不包含alloc、 new 、copy等,則這些方法返回的對象都是autorelease的,如[NSDate date]。
- 開發(fā)中經(jīng)常會寫一些類方法來快速創(chuàng)建一個autorelease對象,創(chuàng)建對象時不要直接使用類名,而是使用self。
4. 錯誤寫法
- 連續(xù)調(diào)用多次autorelease,釋放池銷毀時執(zhí)行兩次release(-1嗎?)
- Alloc之后調(diào)用了autorelease,之后又調(diào)用了release。

