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