Objective-C內存管理

ARC工做原理

手動內存管理的機理你們應該已經很是清楚了,簡單來講,只要遵循如下三點就能夠在手動內存管理中避免絕大部分的麻煩:objective-c

若是須要持有一個對象,那麼對其發送retain 若是以後再也不使用該對象,那麼須要對其發送release(或者autorealse) 每一次對retain,alloc或者new的調用,須要對應一次release或autorealse調用設計模式

初學者可能僅僅只是知道這些規則,可是在實際使用時不免犯錯。可是當開發者常用手動引用計數 Manual Referecen Counting(MRC)的話,這些規則將逐漸變爲本能。你會發現少一個release的代碼怎麼看怎麼彆扭,從而減小或者杜絕內存管理的錯誤。能夠說MRC的規則很是簡單,可是同時也很是容易出錯。每每很小的錯誤就將引發crash或者OOM之類的嚴重問題。api

在MRC的年代裏,爲了不不當心忘寫release,Xcode提供了一個很實用的小工具來幫助可能存在的代碼問題(Xcode3裏默認快捷鍵Shift+A?不記得了),能夠指出潛在的內存泄露或者過多釋放。而ARC在此基礎上更進一步:ARC是Objective-C編譯器的特性,而不是運行時特性或者垃圾回收機制,ARC所作的只不過是在代碼編譯時爲你自動在合適的位置插入releaseautorelease,就如同以前MRC時你所作的那樣。所以,至少在效率上ARC機制是不會比MRC弱的,而由於能夠在最合適的地方完成引用計數的維護,以及部分優化,使用ARC甚至能比MRC取得更高的運行效率。app

ARC機制

學習ARC很簡單,在MRC時代你須要本身retain一個想要保持的對象,而如今不須要了。如今惟一要作的是用一個指針指向這個對象,只要指針沒有被置空,對象就會一直保持在堆上。當將指針指向新值時,原來的對象會被release一次。這對實例變量,synthesize的變量或者局部變量都是適用的。好比函數

NSString *firstName = self.textField.text;

firstName如今指向NSString對象,這時這個對象(textField的內容字符串)將被hold住。好比用字符串@「OneV"做爲例子(雖然實際上不該該用字符串舉例子,由於字符串的retainCount規則其實和普通的對象不同,你們就把它看成一個普通的對象來看吧…),這個時候firstName持有了@"OneV"。工具

一個strong指針

固然,一個對象能夠擁有不止一個的持有者(這個相似MRC中的retainCount>1的狀況)。在這個例子中顯然self.textField.text也是@「OneV",那麼如今有兩個指針指向對象@"OneV」(被持有兩次,retainCount=2,其實對NSString對象說retainCount是有問題的,不過anyway~就這個意思而已.)。學習

兩個strong指向同一個對象

過了一下子,也許用戶在textField裏輸入了其餘的東西,那麼self.textField.text指針顯然如今指向了別的字符串,好比@「onevcat",可是這時候原來的對象已然是存在的,由於還有一個指針firstName持有它。如今指針的指向關係是這樣的:優化

其中一個strong指向了另外一個對象

只有當firstName也被設定了新的值,或者是超出了做用範圍的空間(好比它是局部變量可是這個方法執行完了或者它是實例變量可是這個實例被銷燬了),那麼此時firstName也再也不持有@「OneV",此時再也不有指針指向@"OneV",在ARC下這種情況發生後對象@"OneV"即被銷燬,內存釋放。atom

沒有strong指向@

相似於firstNameself.textField.text這樣的指針使用關鍵字strong進行標誌,它意味着只要該指針指向某個對象,那麼這個對象就不會被銷燬。反過來講,ARC的一個基本規則便是,只要某個對象被任一strong指針指向,那麼它將不會被銷燬。若是對象沒有被任何strong指針指向,那麼就將被銷燬。在默認狀況下,全部的實例變量和局部變量都是strong類型的。能夠說strong類型的指針在行爲上和MRC時代retain的property是比較類似的。設計

既然有strong,那確定有weak咯~weak類型的指針也能夠指向對象,可是並不會持有該對象。好比:

__weak NSString *weakName = self.textField.text

獲得的指向關係是:

一個strong和一個weak指向同一個對象

這裏聲明瞭一個weak的指針weakName,它並不持有@「onevcat"。若是self.textField.text的內容發生改變的話,根據以前提到的"只要某個對象被任一strong指針指向,那麼它將不會被銷燬。若是對象沒有被任何strong指針指向,那麼就將被銷燬」原則,此時指向@「onevcat"的指針中沒有strong類型的指針,@"onevcat"將被銷燬。同時,在ARC機制做用下,全部指向這個對象的weak指針將被置爲nil。這個特性至關有用,相信無數的開發者都曾經被指針指向已釋放對象所形成的EXCBADACCESS困擾過,使用ARC之後,不管是strong仍是weak類型的指針,都再也不會指向一個dealloced的對象,從根源上解決了意外釋放致使的crash

strong指向另外對象,內存釋放,weak自動置nil

不過在大部分狀況下,weak類型的指針可能並不會很經常使用。比較常見的用法是在兩個對象間存在包含關係時:對象1有一個strong指針指向對象2,並持有它,而對象2中只有一個weak指針指回對象1,從而避免了循環持有。一個常見的例子就是oc中常見的delegate設計模式,viewController中有一個strong指針指向它所負責管理的UITableView,而UITableView中的dataSourcedelegate指針都是指向viewController的weak指針。能夠說,weak指針的行爲和MRC時代的assign有一些類似點,可是考慮到weak指針更聰明些(會自動指向nil),所以仍是有所不一樣的。細節的東西咱們稍後再說。

一個典型的delegate設計模式

注意相似下面的代碼彷佛是沒有什麼意義的:

__weak NSString *str = [[NSString alloc] initWithFormat:…];  
NSLog(@"%@",str); //輸出是"(null)"

因爲strweak,它不會持有alloc出來的NSString對象,所以這個對象因爲沒有有效的strong指針指向,因此在生成的同時就被銷燬了。若是咱們在Xcode中寫了上面的代碼,咱們應該會獲得一個警告,由於不管什麼時候這種狀況彷佛都是不太可能出現的。你能夠把weak換成strong來消除警告,或者直接前面什麼都不寫,由於ARC中默認的指針類型就是strong

property也能夠用strongweak來標記,簡單地把原來寫retainassign的地方替換成strong或者weak就能夠了。

@property (nonatomic, strong) NSString *firstName; 
@property (nonatomic, weak) id  delegate;

ARC能夠爲開發者節省不少代碼,使用ARC之後不再須要關心何時retain,何時release,可是這並不意味你能夠不思考內存管理,你可能須要常常性地問本身這個問題:誰持有這個對象?

好比下面的代碼,假設array是一個NSMutableArray而且裏面至少有一個對象:

id obj = [array objectAtIndex:0];  
[array removeObjectAtIndex:0]; 
NSLog(@"%@",obj);

在MRC時代這幾行代碼應該就掛掉了,由於array中0號對象被remove之後就被當即銷燬了,所以obj指向了一個dealloced的對象,所以在NSLog的時候將出現EXCBADACCESS。而在ARC中因爲obj是strong的,所以它持有了array中的首個對象,array再也不是該對象的惟一持有者。即便咱們從array中將obj移除了,它也依然被別的指針持有,所以不會被銷燬。

一點提醒

ARC也有一些缺點,對於初學者來講,可能僅只能將ARC用在objective-c對象上(也即繼承自NSObject的對象),可是若是涉及到較爲底層的東西,好比Core Foundation中的malloc()或者free()等,ARC就鞭長莫及了,這時候仍是須要本身手動進行內存管理。在以後咱們會看到一些這方面的例子。另外爲了確保ARC能正確的工做,有些語法規則也會由於ARC而變得稍微嚴格一些。

ARC確實能夠在適當的地方爲代碼添加retain或者release,可是這並不意味着你能夠徹底忘記內存管理,由於你必須在合適的地方把strong指針手動設置到nil,不然app極可能會oom。簡單說仍是那句話,你必須時刻清醒誰持有了哪些對象,而這些持有者在何時應該變爲指向nil

ARC必然是Objective-C以及Apple開發的趨勢,從此也會有愈來愈多的項目採用ARC(甚至不排除MRC在將來某個版本被棄用的可能),Apple也一直鼓勵開發者開始使用ARC,由於它確實能夠簡化代碼並加強其穩定性。能夠這麼說,使用ARC以後,因爲內存問題形成的crash基本就是過去式了(OOM除外 :P)

咱們正處於由MRC向ARC轉變的節點上,所以可能有時候咱們須要在ARC和MRC的代碼間來回切換和適配。Apple也想到了這一點,所以爲開發這提供了一些ARC和非ARC代碼混編的機制,這些也將在以後的例子中列出。另外ARC甚至能夠用在C++的代碼中,而經過遵照一些代碼規則,iOS 4裏也可使用ARC(雖然我我的認爲在如今iOS 6都呼之欲出的年代已經基本沒有須要爲iOS 4作適配的必要了)、

總之,聰明的開發者總會嘗試儘量的自動化流程,已減輕本身的工做負擔,而ARC偏偏就爲咱們提供了這樣的好處:自動幫咱們完成了不少之前須要手動完成的工做,所以對我來講,轉向ARC是一件不須要考慮的事情。



引用關鍵字

ARC中關於對象的引用參照,主要有下面幾關鍵字。使用strong, weak, autoreleasing限定的變量會被隱式初始化爲nil。

 

  • __strong

變量聲明缺省都帶有__strong關鍵字,若是變量什麼關鍵字都不寫,那麼缺省就是強參照。

 

  • __weak

上面已經看到了,這是弱參照的關鍵字。該概念是新特性,從 iOS 5/ Mac OS X 10.7 開始導入。因爲該類型不影響對象的生命週期,因此若是對象以前就沒有持有者,那麼會出現剛建立就被破棄的問題,好比下面的代碼。

 

C代碼   收藏代碼
  1. NSString __weak *string = [[NSString alloc] initWithFormat:@"First Name: %@", [self firstName]];    
  2. NSLog(@"string: %@", string); //此時 string爲空   

 

若是編譯設定OS版本 Deployment Target 設定爲這比這低的版本,那麼編譯時將報錯(The current deployment target does not support automated __weak references),這個時候,咱們可使用下面的 __unsafe_unretained。

弱參照還有一個特徵,即當參數對象失去全部者以後,變量會被自動付上nil (Zeroing)。

 

  • __unsafe_unretained

該關鍵字與__weak同樣,也是弱參照,與__weak的區別只是是否執行nil賦值(Zeroing)。可是這樣,須要注意變量所指的對象已經被破棄了,地址還還存在,但內存中對象已經沒有了。若是仍是訪問該對象,將引發「BAD_ACCESS」錯誤。

 

  • __autoreleasing

該關鍵字使對像延遲釋放。好比你想傳一個未初始化的對像引用到一個方法當中,在此方法中實例化此對像,那麼這種狀況可使用__autoreleasing。他被常常用於函數有值參數返回時的處理,好比下面的例子。

  
C代碼   收藏代碼
  1. - (void) generateErrorInVariable:(__autoreleasing NSError **)paramError {    
  2.     ....    
  3.     *paramError = [[NSError alloc] initWithDomain:@"MyApp" code:1 userInfo:errorDictionary];    
  4. }    
  5.    
  6. ....    
  7. {    
  8.     NSError *error = nil;    
  9.     [self generateErrorInVariable:&error];    
  10.     NSLog(@"Error = %@", error);    
  11. }    
 

又如函數的返回值是在函數中申請的,那麼但願釋放是在調用端時,每每有下面的代碼。

 

C代碼   收藏代碼
  1. -(NSString *)stringTest    
  2. {    
  3.     NSString *retStr = [NSString stringWithString:@"test"];    
  4.    
  5.     return [[retStr retain] autorelease];    
  6. }    
  7.    
  8. // 使用ARC    
  9.    
  10. -(NSString *)stringTest    
  11. {    
  12.     __autoreleasing NSString *retStr = [NSString alloc] initWithString:@"test"];    
  13.    
  14.     return retStr;    
  15. }    
 

 

即當方法的參數是id*,且但願方法返回時對象被autoreleased,那麼使用該關鍵字。

 

補充一個 循環應用問題

首先delegate要使用assign而不是retain,這個問題你們經過看iOS的api就能夠了,最典型的是tabView裏面的delegate和datasource都是用的assign。
 那爲何要使用assign而不是retain呢?
 首先,考慮類的設計模式,類與類只見的大致關係有繼承和聚合的關係,當咱們使用聚合的時候該對象就擁有聚合的對象,這時候咱們就須要retain使引用計數器+1來控制該對象的內存管理,因此個人感受retain和copy的一項能力就是擁有該對象的內存管理權。
下面就得說delegate了,一個對象不必管理本身delegate的生命週期,或者說不必擁有該對象,因此咱們只要知道它的指針就能夠了,用指針找到對象去調用方法,也就是委託實現的感受。

或者咱們換個角度,從內存管理方面也能夠解釋這個問題。delegate的生命週期不須要讓該對象去控制,若是該對象對其使用retain極可能致使delegate所指向的對象沒法正確的釋放。
@循環引用
全部的引用計數系統,都存在循環應用的問題。例以下面的引用關係:
對象a建立並引用到了對象b.
對象b建立並引用到了對象c.
對象c建立並引用到了對象b.
這時候b和c的引用計數分別是2和1。當a再也不使用b,調用release釋放對b的全部權,由於c還引用了b,因此b的引用計數爲1,b不會被釋放。b不釋放,c的引用計數就是1,c也不會被釋放。今後,b和c永遠留在內存中。

 這 種狀況,必須打斷循環引用,經過其餘規則來維護引用關係。好比,咱們常見的delegate每每是assign方式的屬性而不是retain方式 的屬性,賦值不會增長引用計數,就是爲了防止delegation兩端產生沒必要要的循環引用。若是一個UITableViewController 對象a經過retain獲取了UITableView對象b的全部權,這個UITableView對象b的delegate又是a, 若是這個delegate是retain方式的,那基本上就沒有機會釋放這兩個對象了。本身在設計使用delegate模式時,也要注意這點。

由於循環引用而產生的內存泄露也是Instrument沒法發現的,因此要特別當心。

相關文章
相關標籤/搜索