Objective-C中的內存管理機制

從蘋果的官方文檔來看,OC對應用程序的內存管理提供了2種方法。ios

第一種即「manual retain-release」(MRR),手動保留釋放,也可理解爲手動引用計數。程序員

第二種,「Automatic Reference Counting」(ARC),自動引用計數。可是ARC並不等同垃圾回收。在蘋果的官方文檔有這樣一句話,「You are strongly encouraged to use ARC for new projects.」意思是蘋果強烈建議在項目中運用ARC機制來管理內存。算法

內存管理不當的話會出現如下2種問題:安全

1.過早釋放:在某處程序用完某塊內存以前,就將該內存還給了「堆」。(這裏的堆指的是,ios啓動應用時,會爲應用保留一部分空閒的RAM,這部分空閒的RAM稱爲堆。應用程序可隨意使用堆,不會影響ios的其餘部分,也不會影響其餘應用。)oop

2.內存泄露:不釋放已經不使用的內存會致使內存泄露,即便他歷來沒有被再次使用過。內存泄露會致使你的應用程序的內存使用量日益增長,這反過來有可能會致使系統性能較差或申請內存被終止。性能

要說明的一點是不論是MRR中的「經過屬性機制簡化存取方法」(在「存」方法中涉及到了基本的內存管理),仍是ARC。本質上都是蘋果幫助程序員在開發時減小了代碼量,把原來由程序員要完成的工做交給編譯器去完成,從而減小軟件開發的繁瑣程度。優化

接下來就MRR和ARC進行詳細的說明。ui

在OC中全部的類均繼承於基類NSObject,那麼全部的類就都有一個類方法alloc和一個實例方法dealloc。當經過向類發送alloc方法來建立類實例時,系統會從堆中分配出相應字節數的內存(注:指針類型的實例變量大小是4個字節,這是保存堆中的對象地址所需的內存空間)。例如:UIView *view = [[UIView alloc] init]; alloc會返回一個指針對象,指向新分配的內存。分配內存後,在類完成其「功能」後,還要將內存還給堆。可是不能夠直接向對象發送dealloc方法,即這樣寫[view dealloc]是不對的,只能由對象本身向本身發送dealloc方法。對於dealloc,蘋果官方文檔是這麼解釋的:The NSObject class also defines a method, dealloc, that is invoked automatically when an object is deallocated(NSObject定義了一個方法dealloc,當對象被釋放時自動調用)。那麼對象什麼時候釋放?釋放時是否安全呢?這個在OC中是經過引用計數來解決這個問題的。spa

一.引用計數算法

對象建立後,這個對象就有一個全部者。對象在其生命週期能夠有不一樣的全部者,也能夠同時有多個全部者,引用計數既是用來記錄全部者的數量。當對象沒有全部者時,即引用計數爲0時,就會釋放本身。做爲對象自己不須要知道全部者是誰,只需知道全部者的個數。對象經過retain計數跟蹤全部者的數量。這個是經過NSObject定義的協議與標準方法命名約定相結合的方法來實現的。引用計數實際上是在進行「責任落實」:誰建立了對象(或保留了已經建立的對象),誰就是該對象的全部者。釋放對象即放棄該對象的全部權。誰有對象的全部權,誰就要負責放棄該全部權。在不能再向相應對象發送消息時,即再也不擁有指向該對象的指針時,須要放棄該全部權。但此算法沒法回收循環引用的存儲對象。Cocoa目前採用的就是此種機制。(Cocoa是蘋果公司爲Mac OS X所建立的原生面向對象的API,是Mac OS X上五大API之一,其它四個是Carbon、POSIX、X11和Java)指針

manual retain-release(MRR)

MRR能夠理解爲當對象建立後,會有一個全部者,即新建對象的retain計數是1.當對象獲得某個全部者時,retain計數+1,當對象失去某個全部者時,調用release方法,retain計數-1.當對象沒有任何全部者時,retain計數爲0.對象會自動調用dealloc,將所佔用的內存還給堆。用代碼來實現就是:

- (id)retain
{
    retainCount++;
    return self;
}
- (void)release
{
    retainCount--;
    if(retainCount == 0){
        [self dealloc];
    } 
}
複製代碼

何爲全部者?何爲擁有該對象的全部權?就是當你在OC中用「alloc」, 「new」, 「copy」, or 「mutableCopy」方法建立對象後,便是此對象的擁有者。還有就是當你在保留某一對象的值的時候,也是擁有了該對象(屬性中的set方法深入的說明了這一點)。以nane屬性爲例,它的set方法應該寫爲:

- (void)setName:(NSString *)str
{
    [str retain];
    [name release];
    name = str;
}
複製代碼

這裏必須先保留新對象,再釋放當前對象。這是由於name和str有可能指向同一個對象。若是顛倒順序,就有可能釋放掉本來打算做爲name保留的對象。在類中,當類擁有其它實例對象的時候,要在dealloc方法中將其release掉。

引用計數的規則:

1.若是用來建立對象的方法,其方法名是以alloc或new開頭的,或是包含copy和mutableCopy,那麼你已經擁有該對象的全部權。你要負責在不須要該對象的時候將其釋放。

2.若是你不擁有某個對象,可是要確保該對象繼續存在,那麼能夠經過向其發送retain消息來得到全部權(retain計數+1)。

3.當你擁有某個對象而且再也不須要該對象的時候,要release或autorelea掉。(下面會詳細介紹autorelease)

4.只要對象還有至少一個全部者,該對象就會繼續存在下去,只有在retain計數爲0時,纔會收到dealloc消息。

使用自動釋放池(autorelease)

蘋果官網是這樣解釋的:自動釋放池塊提供了一種機制,讓你能夠放棄對象的全部權,但要避免它被當即釋放(例如,當您返回一個對象的類方法)的可能性。一般狀況下,你並不須要建立本身的autorelease池塊。在OC中的類方法,是爲「他人」建立對象,「本身」不擁有,也不使用。那類方法中對象的內存怎麼管理呢?這裏須要某種解決方案,可以暫時不釋放對象,但具有釋放該對象的權利。經過向對象發送autorelease消息,能夠將對象標記爲「稍後釋放」。當對象收到autorelease後,不會立刻釋放,而是會加入一個NSAutoreleasePool實例。該NSAutoreleasePool實例會記錄全部標記爲「稍後釋放」的對象。每隔一段時間,這個NSAutoreleasePool實例會被「排幹(drain)」,這時它會向其包含的全部對象發送release消息,而後移除這些對象。

標記爲autorelease的對象有2種命運:要麼走完對象的生命週期,直到被釋放,要麼被另一個對象保留。當某一對象保留了標記爲autorelease的對象後,那麼他的retain count計數會變成2,未來的某個時候,NSAutoreleasePool實例會釋放該對象,使其retain count計數爲1.何爲「未來的某個時候」?ios應用在運行時,存在一個運行循環(run loop)。該運行循環等待事件(event)的發生,例如觸摸事件或定時器觸發(NSTimer)等等,當事件發生時,應用會跳出運行循環並經過調用某個類方法來處理相應的事件。代碼執行完畢後,應用將返回當前的運行循環。每次循環結束,全部標記爲autorelease的對象都會收到release消息。

Automatic Reference Counting(ARC)

ARC機制極大的減小了開發過程當中常見的程序錯誤:retain跟release不匹配。ARC並不會消除對retain和release的調用,而是把這項本來大都屬於開發者的工做移交給了編譯器。ARC並不等同於垃圾回收。retain和release仍然會被調用,因此有一些開銷,在release的時候可能還會調用dealloc方法。這段代碼與程序員手動調用retain和release的代碼在運行結果上是徹底一致的。垃圾回收機制是在運行時起做用的,會影響運行效率,而ARC是在編譯時插入內存管理代碼,不影響運行時效率,所以內存回收比垃圾回收時的效率要高,可以提高系統性能。這種編譯器能夠自由地以多種方式優化內存管理,而讓程序員手動去作這些工做是不現實的。在多數狀況下,使用ARC生成內存管理代碼的程序比程序員手工添加內存管理代碼的對等程序運行更快!

ARC不是垃圾回收,尤爲是它不能像Snow Leopard中的垃圾回收機制那樣處理循環引用。所以,在ios開發中,必需要作好對強引用(strong reference)的跟蹤管理以避免出現循環引用。屬性關係有兩種主要類型:strong和weak。至關於非ARC環境裏的retain和assign。只要存在一個強引用,對象就會一直存在,不會被銷燬。OC中一直存在循環引用的問題,但在實際應用中不多出現循環引用。對於過去那些使用assign屬性的地方,在ARC環境中要使用weak代替。大部分引用循環是由委託(delegate)引發的,因此應該老是把delegate屬性聲明爲weak。當引用的對象被銷燬以後,weak引用會被自動設置爲nil,與assign相比這是一個巨大的進步,由於assign能夠指向被釋放掉的內存,致使程序奔潰。

二.可達性分析算法

近現代的垃圾回收實現方法,經過按期對若干根儲存對象開始遍歷,對整個程序所擁有的儲存空間查找與之相關的存儲對象和沒相關的存儲對象進行標記,而後將沒相關的存儲對象所佔物理空間回收。既經過一系列的GC Roots的對象做爲起始點,從這些根節點開始向下搜索,搜索所走過的路徑稱爲引用鏈(Reference Chain),當一個對象到GC Roots沒有任何引用鏈相連時,則證實此對象是不可用的。便是可回收的。此算法可回收循環引用的存儲對象。(Java和C#語言採用的機制)


引伸閱讀:深拷貝和淺拷貝

深拷貝:簡單說就是對指針指向的內容進行拷貝,以字符串爲例,就是指建立一個新的指針在一個新的地址區域建立一個字符串,這個字符串與原字符串值相同,新的指針指向這個新建立的字符串。而原字符串的引用計數沒有+1

淺拷貝:既指針拷貝,例如一個指針指向一個字符串,也就是說這個指針變量的值是這個字符串的地址,那麼對這個指針拷貝就是又建立了一個指針變量,這個指針變量的值是這個字符串的地址,也就是這個字符串的引用計數+1

關於深淺拷貝看源碼一目瞭然,以NSString和NSMutableString爲例:

- (id)copyWithZone:(NSZone*)zone
{
  if (NSStringClass == Nil) NSStringClass = [NSString class];
  return RETAIN(self)        
}

- (id)mutableCopyWithZone:(NSZone*)zone
{
  return [[NSMutableString allocWithZone:zone] initWithString:self];  
}
複製代碼

看上面代碼,當屬性設置copy時,實際調用的就是copyWithZone方法,而copyWithZone並無建立新的對象,而是使指針持有了原來的對象,即淺拷貝。而屬性設置mutableCopy時,調用的就是mutableCopyWithZone方法,而這個方法建立了一個新的可變字符串對象,即深拷貝。

相關文章
相關標籤/搜索