我們討論過PRoperties 后,所有的內(nèi)存管理系統(tǒng)都是通過控制所有對象的生命周期來減少內(nèi)存的占用。iOS和OS X應(yīng)用程序完成這些是通過對象擁有者來實(shí)現(xiàn)的,它保證了只要對象使用就會存在,但是不長。
這種對象擁有者的模式來自于引用計(jì)數(shù)系統(tǒng),它會記錄對象現(xiàn)在被多少對象擁有,當(dāng)你生命一個對象的擁有者,你要增加它的計(jì)數(shù),而當(dāng)你不用這個對象的時候,你需要減少這個計(jì)數(shù)。只要它的引用計(jì)數(shù)大于0,對象就一定會存在。但是一旦計(jì)數(shù)為0,操作系統(tǒng)就會被允許釋放它。
在過去,開發(fā)者通常通過調(diào)用一個被NSObject protocol定義的特殊的內(nèi)存管理方法來控制對象的引用計(jì)數(shù)。這個方法叫做Manual Retain Release(MRR),也就是手動保持釋放。然而,到了Xcode4.2之后介紹了Automatic Reference Countin(ARC),就是自動引用釋放。ARC自動地插入了所有他們的方法。
如今的應(yīng)用程序應(yīng)該總是使用ARC,
因?yàn)樗涌煽慷沂鼓銓W⒂谀愕腁pp特性而不是內(nèi)存管理。
該文章主要解釋引用計(jì)數(shù)概念里面的MRR,然后討論一些特別需要關(guān)注的關(guān)于ARC的一些知識。
在手動保持釋放環(huán)境中,持有和放棄每個對象的所有權(quán)是我們的工作。你實(shí)現(xiàn)這些需要調(diào)用一些特殊的內(nèi)存相關(guān)方法,下面就是用到的方法以及簡短描述
方法 | 行為 |
---|---|
alloc | 創(chuàng)建一個對象并且聲明它的所有權(quán) |
retian | 聲明一個存在對象的所有權(quán) |
copy | 復(fù)制一個對象,然后聲明它的所有權(quán) |
release | 放棄一個對象的所有權(quán),然后立刻銷毀它 |
autorelease | 放棄對象的所有權(quán),但是延遲銷毀。(最終也是銷毀) |
手動的控制一個對象的所有權(quán)貌似看起來是一件令人害怕的任務(wù),但是其實(shí)它非常容易。你要做的就是聲明任何你需要對象的所有權(quán),當(dāng)你不再使用的時候記得放棄所有權(quán)就行了。從實(shí)用的角度來看,意味著你必須平衡alloc,retain和copy的調(diào)用使用release或者autorelease再相同的對象上。
如果你忘記了release一個對象,那么它的潛在的內(nèi)存將不會被釋放,這樣就會造成內(nèi)存泄露。如果泄露嚴(yán)重就會導(dǎo)致程序崩潰。相反如果你調(diào)用release太多次數(shù)的話,會產(chǎn)生野指針(懸掛指針)。當(dāng)你試圖訪問懸掛指針的時候,你就會請求一個無效的內(nèi)存地址,這樣你的程序很有可能崩潰。
下面來解釋一樣如何通過合理的使用上面提到的方法來避免內(nèi)存泄露和懸掛指針。
在內(nèi)存管理(1)中我們介紹過了,就不在介紹了。
我們已經(jīng)知道使用alloc創(chuàng)建一個對象。但是,它不僅僅是給對象分配了內(nèi)存,它也設(shè)置它的引用計(jì)數(shù)為1.這也合理,因?yàn)槲覀內(nèi)绻幌氤钟羞@個對象(持有一小會兒也算)那么我們就沒有必要去創(chuàng)建對象。
#import <Foundation/Foundation.h>int main(int argc, const char * argv[]) { @autoreleasepool { NSMutableArray *mutableArray = [[NSMutableArray alloc] init]; [mutableArray addObject:@"Scott"]; NSLog(@"%@",mutableArray); } return 0;}
上述帶么我們肯定很熟悉,就是實(shí)例化一個可變數(shù)組,添加一個value,然后打印它的內(nèi)容。從內(nèi)存管理的角度來看,我們現(xiàn)在有一個mutableArr對象,這就意味著后面我們必須在某個地方要release它。
但是,在代碼中我們沒有釋放它,我們的代碼現(xiàn)在就有一個內(nèi)存泄露。我們可以通過Product-->Analyze測試出來。結(jié)果如下:
這就有一個問題,它可以在上圖中看到(main.m)。
這是一個很小的對象,因此泄露不太致命。然而,如果他一次又一次地發(fā)生(例如在一個循環(huán)中或者用戶一直點(diǎn)擊一個按鈕),那么這個程序就會最終用完內(nèi)存,然后崩潰。
該方法通過放棄對象的所有權(quán)來減少引用計(jì)數(shù)。因此,我們可以解決內(nèi)存泄露問題通過在NSLog()后面添加下面的代碼:
[mutableArray release];
現(xiàn)在我們的alloc和release就平衡了,靜態(tài)分析就不會報(bào)出任何錯誤。典型的,你將會想放棄對象的所有權(quán)在同樣的方法之后。
過早釋放會造成懸掛指針。例如,在NSLog()前面去調(diào)用release方法。因?yàn)閞elease會立刻釋放占用的內(nèi)存,mutableArray變量在NSLog()里面就指向了一塊不可用的內(nèi)存,然后你的程序就會崩潰:EXC_BAD_access error code 當(dāng)你想運(yùn)行的時候。(最新的Xcode現(xiàn)在直接打印出來空,而沒有提示錯誤。)
因此,不要在還在使用一個對象的時候而去釋放它。
我們現(xiàn)在新建一個對象CarStore。
CarStore.h
#import <Foundation/Foundation.h>@interface CarStore : NSObject- (NSMutableArray *)inventArr;- (void)setInventArr:(NSMutableArray *)newInventArr;@end
CarStore.m
#import "CarStore.h"@implementation CarStore{ NSMutableArray *_inventArr;}- (NSMutableArray *)inventArr { return _inventArr;}- (void)setInventArr:(NSMutableArray *)newInventArr { _inventArr = newInventArr;}@end
然后我們在main.m中進(jìn)行如下操作:
#import <Foundation/Foundation.h>#import "CarStore.h"int main(int argc, const char * argv[]) { @autoreleasepool { NSMutableArray *mutableArray = [[NSMutableArray alloc] init]; [mutableArray addObject:@"Scott"]; CarStore *superStore = [[CarStore alloc] init]; [superStore setInventArr:mutableArray]; [mutableArray release]; NSLog(@"%@",[superStore inventArr]); } return 0;}
此時我們會發(fā)現(xiàn)里面是沒有數(shù)據(jù)的。因?yàn)榇藭rinventArr就是一個懸掛指針,因?yàn)閷ο笠呀?jīng)被released了在main.m中。現(xiàn)在,superstore對象有個弱引用array.為了實(shí)現(xiàn)強(qiáng)引用,CarStore需要聲明數(shù)組所有權(quán):
- (void)setInventArr:(NSMutableArray *)newInventArr { _inventArr = [newInventArr retain];}
這樣就確保了inventArr沒有被釋放當(dāng)superstore正在使用他的時候。可以看一下:retain方法返回是對象的本身,這就是我們執(zhí)行retain和指派任務(wù)在一行。
然而這又造成了另一個問題:retain調(diào)用沒有平衡release,因此會產(chǎn)生另一個內(nèi)存泄露。當(dāng)我們傳遞過數(shù)組之后,我們不能訪問老得數(shù)值,這就意味著我們將不會使用它,為了解決這個問題,我們需要調(diào)用release去釋放老值: //不太理解,依然會報(bào)內(nèi)存泄露
- (void)setInventArr:(NSMutableArray *)newInventArr { if (_inventArr==newInventArr) { return; } NSMutableArray *oldValue = _inventArr; _inventArr = [newInventArr retain]; [oldValue release];}
這就是tetain和strong屬性做的事情,使用property將會更方便。
由此可知alloc和retain必須和release平衡,確保內(nèi)存最終被釋放。
另一種保留是復(fù)制的方法,它創(chuàng)建了一個新實(shí)列對象和增加了引用計(jì)數(shù),保留最初的影響。因此,如果你想復(fù)制mutalbeArr,而不是指向可變的,你可以修改setInventory方法如下:
- (void)setInventArr:(NSMutableArray *)newInventArr { if (_inventArr==newInventArr) { return; } NSMutableArray *oldValue = _inventArr; _inventArr = [newInventArr copy]; [oldValue release];}
一些類支持多重復(fù)制方法(例如多重init方法)。任何copy的對象具有相同的行為。
就像release一樣,autorelease方法放棄對象的所有權(quán),但是不是立刻銷毀對象,它延遲釋放內(nèi)存。這樣允許你當(dāng)你應(yīng)該釋放對象的時候釋放。
例如,考慮一下一個簡單的工廠方法:
+ (CarStore *)carStore;
從技術(shù)上講,是carStore方法對對象的釋放負(fù)責(zé),因?yàn)檎{(diào)用者不知道該方法擁有返回對象。因此,它應(yīng)該實(shí)現(xiàn)返回一個自動釋放的對象,就像下邊:
+ (CarStore *)carStore { CarStore *newStore = [[CarStore alloc] init]; return [newStore autorelease];}
那么這個對象就放棄了所有權(quán),延遲釋放,,然后會調(diào)用正常的release方法。這就是為什么main():
它確保當(dāng)程序結(jié)束運(yùn)行的時候,自動釋放對象被銷毀。
在ARC之前,它是一個很方便的方式,因?yàn)樗屇銊?chuàng)建對象但是不用擔(dān)心在后面什么時候哪里去調(diào)用release。
如果你改變superstore的創(chuàng)建方式,使用下邊的:
// CarStore *superStore = [[CarStore alloc] init]; CarStore *superStore = [CarStore carStore];
實(shí)際上,你不允許釋放這個superstore實(shí)例了現(xiàn)在,因?yàn)槟悴辉贀碛兴?-而是carStore工程方法擁有它。一定不要去release一個autorelease對象,否則你會產(chǎn)生懸掛指針,甚至使程序崩潰。
該方法和init方法相同。它被合理的使用在對象銷毀之前。給你個機(jī)會來清理任何內(nèi)部的對象們。這個方法通過runtime自動調(diào)用------
你不應(yīng)該去自己調(diào)用dealloc。
在MRR環(huán)境中,最常見的你需要做的就是使用dealloc方法去釋放一個實(shí)例變量存儲的對象。想想我們剛才的CarStore當(dāng)一個實(shí)例dealloc時發(fā)生了什么:它的_inventArr(被setter保持著的)從來沒有機(jī)會被釋放。這是另一種形式的內(nèi)存泄露,為了解決這些,我們要做的就是添加一個dealloc方法到CarStore.m中:
- (void)dealloc { [_inventArr release]; [super dealloc];}
你必須要調(diào)用super dealloc去保證父類中的實(shí)例變量在合適的時間去釋放。為了讓dealloc簡單,我們不應(yīng)該在dealloc里面去進(jìn)行邏輯處理。
總之,關(guān)鍵是處理好alloc,retain和copay 和release活著autorelease之間的平衡,否則你就會遇到懸掛指針活著內(nèi)存泄露在你的程序中。
在真實(shí)地路上,上面好多代碼都是廢棄的。但是通過上面可以讓你更好的理解ARC編譯原理。
現(xiàn)在,你已經(jīng)了解了MMR,現(xiàn)在你可以忘記他們了。ARC和MRR的工作方式一樣,但是它自動為你插入合適的內(nèi)存管理方法。這對于OC開發(fā)者是很好地處理。因?yàn)槲覀兛梢园丫Ψ旁趹?yīng)用程序需要做什么而不是如何做。
ARC中人為錯誤的內(nèi)存管理幾乎不存在,所以使用他的唯一理由可能就是使用過去的第三方代碼庫。(然而,ARC大部分情況下向后兼容MRR程序)。下面就是介紹MRR和ARC之間的切換。
Project--->Build Settings--->搜索garbage,找到Objective-C Automatic Reference Counting設(shè)置為YES即可。
ARC通過分析代碼每個對象的理想的生存時間應(yīng)該是多來工作,然后自動的插入必要的retain和release方法。該方式需要完全控制整個程序中對象的所有權(quán),
這就意味著你不允許調(diào)用retain,release或者autorelease
唯一你可能見到的在ARC中的內(nèi)存相關(guān)方法就是alloc和copy。
,你應(yīng)該使用strong來代替retain,使用weak來代替assign。這些已經(jīng)在properties 討論過了。
dealloc在ARC中有一點(diǎn)不同,你不要release實(shí)例變量在dealloc方法中---ARC已經(jīng)為你實(shí)現(xiàn)了。另外,父類的dealloc是自動調(diào)用,你也不需要[super dealloc]
。
但是有一種例外,如果你使用的低層次的內(nèi)存構(gòu)造函數(shù),就像malloc(),那樣的話,你仍然需要調(diào)用free在dealloc中去避免內(nèi)存泄露。
ARC中你唯一要關(guān)系的就是循環(huán)引用。
你應(yīng)該明白了所有你應(yīng)該知道的關(guān)于OC的內(nèi)存管理。
新聞熱點(diǎn)
疑難解答
圖片精選