在Objective-C的記憶體管理中,其實就是引用計數(reference count)的管理。記憶體管理就是在程式需要時程式員配置設定一段記憶體空間,而當使用完之後将它釋放。如果程式員對記憶體資源使用不當,有時不僅會造成記憶體資源浪費,甚至會導緻程式crach。我們将會從引用計數和記憶體管理規則等基本概念開始,然後講述有哪些記憶體管理方法,最後注意有哪些常見記憶體問題。
memory management from apple document
為了解釋引用計數,我們做一個類比:員工在辦公室使用燈的情景。
引用Pro Multithreading and Memory Management for iOS and OS X的圖
當第一個人進入辦公室時,他需要使用燈,于是開燈,引用計數為1;
當另一個人進入辦公室時,他也需要燈,引用計數為2;每當多一個人進入辦公室時,引用計數加1;
當有一個人離開辦公室時,引用計數減1,當引用計數為0時,也就是最後一個人離開辦公室時,他不再需要使用燈,關燈離開辦公室。
從上面員工在辦公室使用燈的例子,我們對比一下燈的動作與Objective-C對象的動作有什麼相似之處:
因為我們是通過引用計數來管理燈,那麼我們也可以通過引用計數來管理使用Objective-C對象。
而Objective-C對象的動作對應有哪些方法以及這些方法對引用計數有什麼影響?
當你alloc一個對象objc,此時RC=1;在某個地方你又retain這個對象objc,此時RC加1,也就是RC=2;由于調用alloc/retain一次,對應需要調用release一次來釋放對象objc,是以你需要release對象objc兩次,此時RC=0;而當RC=0時,系統會自動調用dealloc方法釋放對象。
在開發中,我們常常都會使用到局部變量,局部變量一個特點就是當它超過作用域時,就會自動釋放。而autorelease pool跟局部變量類似,當執行代碼超過autorelease pool塊時,所有放在autorelease pool的對象都會自動調用release。它的工作原理如下:
建立一個NSAutoreleasePool對象;
在autorelease pool塊的對象調用autorelease方法;
釋放NSAutoreleasePool對象。
iOS 5/OS X Lion前的(等下會介紹引入ARC的寫法)執行個體代碼如下:
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
// put object into pool
id obj = [[NSObject alloc] init];
[obj autorelease];
[pool drain];
/* 超過autorelease pool作用域範圍時,obj會自動調用release方法 */
由于放在autorelease pool的對象并不會馬上釋放,如果有大量圖檔資料放在這裡的話,将會導緻記憶體不足。
for (int i = 0; i < numberOfImages; i++)
{
/* 處理圖檔,例如加載
* 太多autoreleased objects存在
* 由于NSAutoreleasePool對象沒有被釋放
* 在某個時刻,會導緻記憶體不足
*/
}
iOS/OS X記憶體管理方法有兩種:手動引用計數(Manual Reference Counting)和自動引用計數(Automatic Reference Counting)。從OS X Lion和iOS 5開始,不再需要程式員手動調用retain和release方法來管理Objective-C對象的記憶體,而是引入一種新的記憶體管理機制Automatic Reference Counting(ARC),簡單來說,它讓編譯器來代替程式員來自動加入retain和release方法來持有和放棄對象的所有權。
在ARC記憶體管理機制中,id和其他對象類型變量必須是以下四個ownership qualifiers其中一個來修飾:
__strong(預設,如果不指定其他,編譯器就預設加入)
__weak
__unsafe_unretained
__autoreleasing
是以在管理Objective-C對象記憶體的時候,你必須選擇其中一個,下面會用一些列子來逐個解釋它們的含義以及如何選擇它們。
如果我想建立一個字元串,使用完之後将它釋放調用,使用MRC管理記憶體的寫法應該是這樣:
NSString *text = @"Hello, world"; //@"Hello, world"對象的RC=1
NSLog(@"%@", text);
[text release]; //@"Hello, world"對象的RC=0
而如果是使用ARC方式的話,就text對象無需調用release方法,而是當text變量超過作用域時,編譯器來自動加入[text release]方法來釋放記憶體。
/*
* 當text超過作用域時,@"Hello, world"對象會自動釋放,RC=0
*/
而當你将text指派給其他變量anotherText時,MRC需要retain一下來持有所有權,當text和anotherText使用完之後,各個調用release方法來釋放。
NSString *anotherText = text; //@"Hello, world"對象的RC=1
[anotherText retain]; //@"Hello, world"對象的RC=2
NSLog(@"%@", anotherText);
[text release]; //@"Hello, world"對象的RC=1
[anotherText release]; //@"Hello, world"對象的RC=0
而使用ARC的話,并不需要調用retain和release方法來持有跟釋放對象。
NSString *anotherText = text; //@"Hello, world"對象的RC=2
* 當text和anotherText超過作用域時,會自動調用[text release]和[anotherText release]方法, @"Hello, world"對象的RC=0
除了當__strong變量超過作用域時,編譯器會自動加入release語句來釋放記憶體,如果你将__strong變量重新賦給它其他值,那麼編譯器也會自動加入release語句來釋放變量指向之前的對象。例如:
NSString *anotherText = @"Sam Lau"; // 由于anotherText對象引用另一個對象@"Sam Lau",那麼就會自動調用[anotherText release]方法,使得@"Hello, world"對象的RC=1, @"Sam Lau"對象的RC=1
* 當text和anotherText超過作用域時,會自動調用[text release]和[anotherText release]方法,
* @"Hello, world"對象的RC=0和@"Sam Lau"對象的RC=0
如果變量var被__strong修飾,當變量var指向某個對象objc,那麼變量var持有某個對象objc的所有權。
前面已經提過記憶體管理的四條規則:
我們總結一下編譯器是按以下方法來實作的:
對于規則1和規則2,是通過__strong變量來實作;
對于規則3來說,當變量超過它的作用域或被指派或成員變量被丢棄時就能實作;
對于規則4,當RC=0時,系統就會自動調用。
其實編譯器根據__strong修飾符來管理對象記憶體。但是__strong并不能解決引用循環(Reference Cycle)問題:對象A持有對象B,反過來,對象B持有對象A;這樣會導緻不能釋放記憶體造成記憶體洩露問題。
舉一個簡單的例子,有一個類Test有個屬性objc,有兩個對象test1和test2的屬性objc互相引用test1和test2:
@interface Test : NSObject
@property (strong, nonatomic) id objc;
@end
Test *test1 = [Test new]; /* 對象a */
/* test1有一個強引用到對象a */
Test *test2 = [Test new]; /* 對象b */
/* test2有一個強引用到對象b */
test1.objc = test2; /* 對象a的成員變量objc有一個強引用到對象b */
test2.objc = test1; /* 對象b的成員變量objc有一個強引用到對象a */
/* 當變量test1超過它作用域時,它指向a對象會自動release
* 當變量test2超過它作用域時,它指向b對象會自動release
*
* 此時,b對象的objc成員變量仍持有一個強引用到對象a
* 此時,a對象的objc成員變量仍持有一個強引用到對象b
* 于是發生記憶體洩露
如何解決?于是我們引用一個__weakownership qualifier,被它修飾的變量都不持有對象的所有權,而且當變量指向的對象的RC為0時,變量設定為nil。例如:
__weak NSString *text = @"Sam Lau";
NSLog(@"%@", text);
由于text變量被__weak修飾,text并不持有@"Sam Lau"對象的所有權,@"Sam Lau"對象一建立就馬上被釋放,并且編譯器給出警告️,是以列印結果為(null)。
是以,針對剛才的引用循環問題,隻需要将Test類的屬性objc設定weak修飾符,那麼就能解決。
@property (weak, nonatomic) id objc;
test1.objc = test2; /* 對象a的成員變量objc不持有對象b */
test2.objc = test1; /* 對象b的成員變量objc不持有對象a */
__unsafe_unretained ownership qualifier,正如名字所示,它是不安全的。它跟__weak相似,被它修飾的變量都不持有對象的所有權,但當變量指向的對象的RC為0時,變量并不設定為nil,而是繼續儲存對象的位址;這樣的話,對象有可能已經釋放,但繼續通路,就會造成非法通路(Invalid Access)。例子如下:
__unsafe_unretained id obj0 = nil;
id obj1 = [[NSObject alloc] init]; // 對象A
/* 由于obj1是強引用,是以obj1持有對象A的所有權,對象A的RC=1 */
obj0 = obj1;
/* 由于obj0是__unsafe_unretained,它不持有對象A的所有權,但能夠引用它,對象A的RC=1 */
NSLog(@"A: %@", obj0);
/* 當obj1超過它的作用域時,它指向的對象A将會自動釋放 */
NSLog(@"B: %@", obj0);
/* 由于obj0是__unsafe_unretained,當它指向的對象RC=0時,它會繼續儲存對象的位址,是以兩個位址相同 */
列印結果是記憶體位址相同:
如果将__unsafe_unretained改為weak的話,兩個列印結果将不同。
__weak id obj0 = nil;
/* 由于obj0是__weak, 當它指向的對象RC=0時,它會自動設定為nil,是以兩個列印結果将不同*/
引入ARC之後,讓我們看看autorelease pool有哪些變化。沒有ARC之前的寫法如下:
引入ARC之後,寫法比之前更加簡潔:
@autoreleasepool {
id __autoreleasing obj = [[NSObject alloc] init];
相比之前的建立、使用和釋放NSAutoreleasePool對象,現在你隻需要将代碼放在@autoreleasepool塊即可。你也不需要調用autorelease方法了,隻需要用__autoreleasing修飾變量即可。
引用Pro Multithreading and Memory Management for iOS and OS X的圖
但是我們很少或基本上不使用autorelease pool。當我們使用XCode建立工程後,有一個app的入口檔案main.m使用了它:
int main(int argc, char * argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
有了ARC之後,新的property modifier也被引入到Objective-C類的property,例如:
@property (strong, nonatomic) NSString *text;
下面有張表來展示property modifier與ownership qualifier的對應關系:
要想掌握iOS/OS X的記憶體管理,首先要深入了解引用計數(Reference Count)這個概念以及記憶體管理的規則;在沒引入ARC之前,我們都是通過retain和release方法來手動管理記憶體,但引入ARC之後,我們可以借助編譯器來幫忙自動調用retain和release方法來簡化記憶體管理和減低出錯的可能性。雖然__strong修飾符能夠執行大多數記憶體管理,但它不能解決引用循環(Reference Cycle)問題,于是又引入另一個修飾符__weak。被__strong修飾的變量都持有對象的所有權,而被__weak修飾的變量并不持有對象所有權。下篇我們介紹使用工具如何解決常見記憶體問題:野指針和記憶體洩露。
<a target="_blank" href="http://book.douban.com/subject/10536953/">Pro Multithreading and Memory Management for iOS and OS X</a>
<a target="_blank" href="https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/MemoryMgmt/Articles/MemoryMgmt.html#//apple_ref/doc/uid/10000011-SW1">Advanced Memory Management Programming Guide</a>