軟件運行時會分配和使用設備的內存資源,因此,在軟件開發的過程中,需要進行內存管理,以保證高效、快速的分配內存,并且在適當的時候釋放和回收內存資源。
一、Objective-C內存管理的對象
iOS開發中,內存中的對象主要有兩類,一類是值類型,比如int、float、struct等基本數據類型,另一類是引用類型,也就是繼承自NSObject類的所有的OC對象。前一種值類型不需要我們管理,后一種引用類型是需要我們管理內存的,一旦管理不好,就會產生非常糟糕的后果。
為什么值類型不需要管理,而引用類型需要管理呢?那是因為他們分配內存方式不一樣。值類型會被放入棧中,他們依次緊密排列,在內存中占有一塊連續的內存空間,遵循先進后出的原則。引用類型會被放到堆中,當給對象分配內存空間時,會隨機的從內存當中開辟空間,對象與對象之間可能會留有不確定大小的空白空間,因此會產生很多內存碎片,需要我們管理。
二、Objective-C管理內存的方式
Objective-C中提供了兩種內存管理機制MRC和ARC,分別提供對內存的手動和自動管理,來滿足不同的需求。
1.MRC(人工引用計數),手動管理內存。
MRC模式下,所有的對象都需要手動的添加retain、release代碼來管理內存。使用MRC,需要遵守誰創建,誰回收的原則。也就是誰alloc,誰release;誰retain,誰release。
當引用計數為0的時候,必須回收,引用計數不為0,不能回收,如果引用計數為0,但是沒有回收,會造成內存泄漏。如果引用計數為0,繼續釋放,會造成野指針。為了避免出現野指針,我們在釋放的時候,會先讓指針=nil。
2.ARC(自動引用計數),自動管理內存。
ARC是iOS5推出的新功能,通過ARC,可以自動的管理內存。在ARC模式下,只要沒有強指針(強引用)指向對象,對象就會被釋放。在ARC模式下,不允許使用retain、release、retainCount等方法。并且,如果使用dealloc方法時,不允許調用【super dealloc】方法。
ARC模式下的property變量修飾詞為strong、weak,相當于MRC模式下的retain、assign。strong代替retain,缺省關鍵詞,代表強引用。weak代替assign,聲明了一個可以自動設置nil的弱引用,但是比assign多了一個功能,指針指向的地址被釋放之后,指針本身也會自動被釋放。
三、與內存有關的修飾符
strong:強引用,ARC中使用,與MRC中retain類似,使用之后,計數器+1。
weak: 弱引用,ARC中使用,如果只想的對象被釋放了,其指向nil,可以有效的避免野指針,其引用計數為1.
readwrite:可讀可寫特性,需要生成getter方法和setter方法時使用。
readonly:只讀特性,只會生成getter方法,不會生成setter方法,不希望屬性在類外改變。
assign:賦值特性,不涉及引用計數,弱引用setter方法將傳人參數賦值給實例變量,僅設置變量時使用。
retain:表示持有特性,setter方法將傳入參數先保留,再賦值,傳入參數的retaincount會+1。
copy:表示拷貝特性,setter方法將傳入對象復制一份,需要完全一份新的變量時。
nonatomic:非原子操作,不加同步,多線程訪問可提高性能,但是線程不安全的。決定編譯器生成的setter,getter是否是原則操作。
atomic:原子操作,同步的,表示多線程安全,與nonatomic相反。
四、MRC與ARC混編
MRC與ARC理論上是不能兼容的,也就是你如果創建的項目是ARC模式的,在你的代碼中是不能使用release,否則會出現內存問題。現在大部分程序都會選擇ARC的方式,但是,很多第三方的框架是MRC模式,如果想把這些第三方的文件加到自己項目中,需要進行標識,否則編譯的時候會出現錯誤。
在ARC的項目中,對MRC的文件可以添加編譯選項-fno-objc-arc的標識;在MRC的項目中,對ARC的文件可以添加編譯選項-fobjc-arc的標識。步驟如下圖所示。
把MRC文件轉為ARC,實際上就是去掉文件中的retain、release,因此也通過下圖中方式完成。
五、關于內存管理經常會涉及的問題
1.僵尸對象、野指針、空指針分別指什么,有什么區別?
僵尸對象:一個OC對象引用計數為0被釋放后就變成僵尸對象了,僵尸對象的內存已經被系統回收,雖然可能改對象還存在,數據依然在內存中,但僵尸對象已經是不穩定對象了,不可以再訪問或者使用,它的內存是隨時可能被別的對象申請而占用的;
野指針:野指針出現的原因是指針沒有賦值,或者指針指向的對象已經被釋放掉了,野指針指向一塊隨機的垃圾內存,向他們發送消息會報EXC_BAD_ACCESS錯誤導致程序崩潰;
空指針:空指針不同于野指針,它是一個沒有指向任何東西的指針,空指針是有效指針,值為nil,NULL,或0等,給空指針發送消息不會報錯,只是不想贏消息而已,應該給野指針及時賦予零值變成有效的空指針,避免內存報錯。
2.Objectvie-C有GC垃圾回收機制嗎?
GC(Garbage Collection),垃圾回收機制,簡單地說就是程序中及時處理廢棄不用的內存對象的機制,防止內存中廢棄對象堆積過多造成內存泄漏。Objective-C語言本身是支持垃圾回收機制的,但有平臺局限性,僅限于Mac桌面系統開發中,而在iPhone和iPad等蘋果移動終端設備中是不支持垃圾回收機制的。在移動設備開發中的內存管理是采用MRC以及iOS5以后的ARC了,本質都是RC引用計數,通過引用計數的方式來管理內存的分配與釋放,從而防止內存泄漏。
另外引用計數RC和垃圾回收GC是有區別的。垃圾回收是宏觀的,對整體進行內存管理,雖然不同平臺垃圾回收機制有異,但基本原理都是一樣的:將所有對象看做一個集合,然后在GC循環中定時檢測活動對象和非活動對象,及時將用不到的非活動對象釋放掉來避免內存泄漏,也就是說用不到的垃圾對象是交給GC來管理釋放的,而無需開發者關心,典型的是Java中的垃圾回收機制;相比于GC,引用計數是局部性的的,開發者要管理控制每個對象的引用計數,打個對象引用計數巍峨0后會馬上被釋放掉。ARC自動引用計數則是一種改進,由編譯器幫助開發者自動管理控制引用計數(自動在核實的時機發送release和retain消息)。另外自動釋放池autorelease pool則像是一個局部的垃圾回收,將部分垃圾對象幾種釋放,相對于單個釋放會有一定延遲。
相關問題:自動釋放池跟GC(垃圾回收)有什么區別?iPhone上有GC嗎?[pool release]和[pool drain]有什么區別?
[pool release]和[pool drain]在作用上是一樣的,都是傾倒自動釋放池,區別是drain在支持GC垃圾回收的系統中(Mac系統)可以引起GC回收操作,而release不可以。對于自動釋放池一般還是使用[pool drain]了,一是它的功能對系統兼容性更強,二者也是為了跟普通對象的release釋放區別開。了自動釋放池的釋放操作指的是向池內所有的對象發送release消息,以讓系統及時釋放池內的所有對象。
3.如果一個對象釋放前被加到了NotificationCenter中,不在NotificationCenter中remove這個對象可能回出現什么問題?
首先對于NotificationCenter的使用,我們都知道,只要添加對象到消息中心進行通知注冊,之后就一定要對其remove進行通知注銷。將對象添加到消息中心后,消息中心只是保存該對象的地址,消息中心到時候回根據地址發送通知給該對象,但并沒有取得該對象的強引用,對象的引用計數不會加1。如果對象釋放后卻沒有從消息中心remove掉進行通知注銷,也就是通知中心還保存著那個指針,而那個指針的對象可能已經被釋放銷毀了,那個指針就成為一個野指針,當通知發生時,會向這個野指針發送消息導致程序崩潰。
4.Objective-C是如何實現內存管理的? autorelease pool自動釋放池是什么? autorelease的對象是在什么時候被release的? autorelease和release有什么區別?
引用計數
Objective-C的內存管理本質上是通過引用計數實現的,每次Runloop都會檢查對象的引用計數,如果引用計數為0,說明該對象已經沒人用了,可以對其進行釋放了。其中引用計數可以大體分三種:MRC(手動內存計數),ARC(自動內存計數,iOS5以后)和內存池。
其中引用計數是如何操作的呢?不論哪種引用計數方式,本質都是在合適的時機將對象的引用計數加1或者減一。
這里簡單歸結一下:
使對象引用計數加1的常見操作有:alloc,copy,retain
使對象引用計數減1的常見操作有:release,autorelease
自動釋放池
自動釋放池是一個統一來釋放一組對象的容器,在向對象發送autorelease消息時,對象并沒有立即釋放,而是將對象加入到最新的自動釋放池(即將該對象的引用交給自動釋放池,之后統一調用release),自動釋放池會在程序執行到作用域結束的位置時進行drain釋放操作,這個時候會對池中的每一個對象都發送release消息來釋放所有對象。這樣其實就實現了這些對象的延遲釋放。
自動釋放吃釋放的時機,也就是自動釋放池內的所有對象是在什么時候釋放的,這里要提到程序的運行周期RunLoop。對于每一個新的RunLoop,系統都會隱式的創建一個autorelease pool,RunLoop結束時自動釋放池變回進行對象釋放操作。
autorelease和release的區別主要是引用計數減一的時機不同,autorelease會在對象的使用真正結束的時候才做引用計數減1,而不是收到消息立馬釋放。
retain,release和autorelease的底層實現
最后通過了解這三者的較底層實現來理解它們的本質區別:
-(id)retain{
//對象引用計數加1
NSIncrementExtraRefCount(self);
return self;
}
-(void)release{
//對象引用計數減1,之后如果引用計數為0則釋放
if(NSDecrementExtraRefCountWasZero(self)){
NSDeallocateObject(self);
}
}
-(id)autorelease{
//添加對象到自動釋放池
【NSAutoreleasePool addObject:self];
return self;
}
5.問題:為什么很多內置的類,如TableViewController的delegate的屬性是assign不是retain?
delegate代理的屬性通常設置為assign或者weak是為了避免循環引用,所有的引用計數系統,都存在循環引用的問題,但也有個別特殊情況,個別類的代理例如CAAnimation的delegate就是使用strong強引用。
其它問法:委托的property聲明用什么屬性?為什么?
6.CAAnimation的delegate代理是強引用還是弱引用?
CAAnimation的代理是強引用,是內存管理中的其中一個罕見的特例。我們知道為了避免循環引用問題,delegate代理一般都使用weak修飾表示弱引用的,而CAAnimation動畫是異步的,如果動畫的代理是弱引用不是強引用的話,會導致其隨時都可能被釋放掉。在使用動畫時要注意采取措施避免循環引用,例如及時在試圖移除之前的合適時機移除動畫。
CAAnimation的代理定義如下,明確說了動畫的代理在動畫對象整個生命周期是被強引用的,默認為nil。
7.問題:OC中,與alloc語義相反的方法是dealloc還是release?與retain語義相反的方法是dealloc還是release?需要與alloc配對使用的方式dealloc還是release,為什么?
alloc與dealloc語義相反,alloc是創建變量,dealloc是釋放變量;
retain與release語義相反,retain保留一個對象,調用后使變量的引用計數加1,而release釋放一個對象,調用后使變量的引用計數減1。
雖然alloc對應dealloc,retain對應release,但是與alloc配對使用的方法是release,而不是dealloc,為什么呢?這要從他們的實際效果來看。事實上alloc和release配對使用知識表象,本質上其實還是retain和release的配對使用。alloc用來創建對象,剛創建的對象默認引用計數為1,相當于調用alloc創建對象過程中同時會調用一次retain使對象引用計數加1,自然要有對應的release的一次調用,使對象在不用時能夠被釋放掉防止內存泄漏。
此外,dealloc是在對象引用計數為0以后系統自動調用的,dealloc沒有使對象引用計數減1的作用,只是在對象引用計數0后被系統調用進行內存回收的收尾工作。
8.內存管理的幾條原則是什么? 按照默認法則,哪些關鍵字生成的對象需要手動釋放?哪些對象不需要手動釋放會自動進入釋放池?在和property結合的時候怎樣有效的避免內存泄漏?
起初MRC中開發者要自己手動管理內存,基本原則是:誰創建,誰釋放,誰引用,誰管理。其中創建主要始于關鍵詞new,alloc和copy的使用,創建并持有開始引用計數為1,如果引用要通過發送retain消息增加引用計數,通過發送release消息減少引用計數,引用計數為0后對象會被系統清理釋放。現在有了ARC后編譯器會自動管理引用計數,開發者不再需要也不可以再手動顯示管理引用計數。