文章結(jié)構(gòu)
1.內(nèi)存管理的基本規(guī)則
2.autoreleasePool
3.ARC管理方法3.1 ARC 引入的四個(gè)ownership qualifiers
3.2 Property(屬性)與ownership qualifier
4.防止內(nèi)存泄漏編程注意事項(xiàng)
更新記錄
一、內(nèi)存管理的基本規(guī)則
在Objective-C的內(nèi)存管理中,其實(shí)就是引用計(jì)數(shù)(reference count)的管理。內(nèi)存管理就是在程序需要時(shí)程序員分配一段內(nèi)存空間,而當(dāng)使用完之后將它釋放。如果程序員對(duì)內(nèi)存資源使用不當(dāng),有時(shí)不僅會(huì)造成內(nèi)存資源浪費(fèi),甚至?xí)?dǎo)致程序crach。我們將會(huì)從引用計(jì)數(shù)和內(nèi)存管理規(guī)則等基本概念開始,然后講述有哪些內(nèi)存管理方法,最后注意有哪些常見內(nèi)存問題。
1.1 引用計(jì)數(shù)(Reference Count)
為了解釋引用計(jì)數(shù),我們做一個(gè)類比:?jiǎn)T工在辦公室使用燈的情景。
- 當(dāng)第一個(gè)人進(jìn)入辦公室時(shí),他需要使用燈,于是開燈,引用計(jì)數(shù)為1
- 當(dāng)另一個(gè)人進(jìn)入辦公室時(shí),他也需要燈,引用計(jì)數(shù)為2;每當(dāng)多一個(gè)人進(jìn)入辦公室時(shí),引用計(jì)數(shù)加1
- 當(dāng)有一個(gè)人離開辦公室時(shí),引用計(jì)數(shù)減1,當(dāng)引用計(jì)數(shù)為0時(shí),也就是最后一個(gè)人離開辦公室時(shí),他不再需要使用燈,關(guān)燈離開辦公室。
形象點(diǎn)的可以這樣解釋,從底層的實(shí)現(xiàn)來(lái)講就是一個(gè)class 結(jié)構(gòu)體,結(jié)構(gòu)體內(nèi)部有個(gè)值(也就是下圖所說(shuō)的retain count
)記錄了對(duì)象擁有者(ownship)的個(gè)數(shù),當(dāng)計(jì)數(shù)值為0時(shí),系統(tǒng)將自動(dòng)釋放這個(gè)對(duì)象占用的內(nèi)存空間。
1.2 內(nèi)存管理基本規(guī)則
從上面員工在辦公室使用燈的例子,我們對(duì)比一下燈的動(dòng)作與Objective-C對(duì)象的動(dòng)作有什么相似之處:
燈的動(dòng)作 | Objective-C對(duì)象的動(dòng)作 |
---|---|
開燈 | 創(chuàng)建一個(gè)對(duì)象并獲取它的所有權(quán)(ownership) |
使用燈 | 獲取對(duì)象的所有權(quán) |
不使用燈 | 放棄對(duì)象的所有權(quán) |
關(guān)燈 | 釋放對(duì)象 |
因?yàn)槲覀兪峭ㄟ^(guò)引用計(jì)數(shù)來(lái)管理燈,那么我們也可以通過(guò)引用計(jì)數(shù)來(lái)管理使用Objective-C對(duì)象。
而Objective-C對(duì)象的動(dòng)作對(duì)應(yīng)有哪些方法以及這些方法對(duì)引用計(jì)數(shù)有什么影響?
Objective-C對(duì)象的動(dòng)作 | Objective-C對(duì)象的方法 |
---|---|
1. 創(chuàng)建一個(gè)對(duì)象并獲取它的所有權(quán) | alloc/new/copy/mutableCopy (RC = 1) |
2. 獲取對(duì)象的所有權(quán) | retain (RC + 1) |
3. 放棄對(duì)象的所有權(quán) | release (RC - 1) |
4. 釋放對(duì)象 | dealloc (RC = 0 ,此時(shí)會(huì)調(diào)用該方法) |
當(dāng)你alloc
一個(gè)對(duì)象objc
,此時(shí)RC=1
;在某個(gè)地方你又retain
這個(gè)對(duì)象objc
,此時(shí)RC
加1,也就是RC=2
;由于調(diào)用alloc/retain
一次,對(duì)應(yīng)需要調(diào)用release
一次來(lái)釋放對(duì)象objc
,所以你需要release
對(duì)象objc
兩次,此時(shí)RC=0
;而當(dāng)RC=0
時(shí),系統(tǒng)會(huì)自動(dòng)調(diào)用dealloc
方法釋放對(duì)象。
除了上面所說(shuō)的alloc/new/copy/mutableCopy/retain
這幾種方法可以獲取對(duì)象的所有權(quán)(ownship)外,當(dāng)對(duì)象被添加到集合對(duì)象(array, dictionary, set)中時(shí),集合對(duì)象會(huì)獲取集合中所有對(duì)象的所有權(quán)(RC+1
),當(dāng)集合對(duì)象釋放時(shí),也會(huì)默認(rèn)向集合中所有對(duì)象發(fā)送release
消息(RC -1)
,符合誰(shuí)創(chuàng)建誰(shuí)釋放的原則。
注意下以下情況是不會(huì)獲取對(duì)象的所有權(quán)的:
(1)不使用alloc/new/copy/mutableCopy
方法引用的對(duì)象將不會(huì)獲取對(duì)象的擁有權(quán)
- (NSString *)fullName {
NSString *string = [NSString stringWithFormat:@"%@ %@",
self.firstName, self.lastName];
return string;
}
這個(gè)函數(shù)調(diào)用的是NSString
的 stringWithFormat
并不不滿足內(nèi)存管理基本原則,不會(huì)擁有對(duì)象的所有權(quán),所以可以放心的返回。而不用調(diào)用release方法或者autorelease方法。
(2)引用對(duì)象指針的地址的方式(they take an argument of type ClassName ** or id *
),不會(huì)獲取對(duì)象的擁有權(quán)。
這個(gè)比較好理解,比如我們常見的error。
NSString *fileName = <#Get a file name#>;
NSError *error;
NSString *string = [[NSString alloc] initWithContentsOfFile:fileName
encoding:NSUTF8StringEncoding error:&error];
if (string == nil) {
// Deal with error...
}
// ...
[string release];
二、 Autorelease Pool
在開發(fā)中,我們常常都會(huì)使用到局部變量,局部變量一個(gè)特點(diǎn)就是當(dāng)它超過(guò)作用域時(shí),就會(huì)自動(dòng)釋放。而autorelease pool跟局部變量類似,當(dāng)執(zhí)行代碼超過(guò)autorelease pool塊時(shí),所有放在autorelease pool的對(duì)象都會(huì)自動(dòng)調(diào)用release
。它的工作原理如下:
創(chuàng)建一個(gè)
NSAutoreleasePool
對(duì)象在autorelease pool塊的對(duì)象調(diào)用
autorelease
方法釋放
NSAutoreleasePool
對(duì)象
iOS 5/OS X Lion前的(等下會(huì)介紹引入ARC的寫法)實(shí)例代碼如下:
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
// put object into pool
id obj = [[NSObject alloc] init];
[obj autorelease];
[pool drain];
/* 超過(guò)autorelease pool作用域范圍時(shí),obj會(huì)自動(dòng)調(diào)用release方法 */
由于放在autorelease pool的對(duì)象并不會(huì)馬上釋放,如果有大量圖片數(shù)據(jù)放在這里的話,將會(huì)導(dǎo)致內(nèi)存不足。
for (int i = 0; i < numberOfImages; i++)
{
/* 處理圖片,例如加載
* 太多autoreleased objects存在
* 由于NSAutoreleasePool對(duì)象沒有被釋放
* 在某個(gè)時(shí)刻,會(huì)導(dǎo)致內(nèi)存不足
*/
}
像上面這種情況你就可以這么寫:
for (int i = 0; i < numberOfImages; i++)
{
@autoreleasepool {
/*
*這樣臨時(shí)的autoreleased objects就會(huì)在autoreleasepool 結(jié)束時(shí)釋放達(dá)到最少的內(nèi)存占用。
*/
}
}
二、 ARC管理方法
iOS/OS X內(nèi)存管理方法有兩種:手動(dòng)引用計(jì)數(shù)(Manual Reference Counting
)和自動(dòng)引用計(jì)數(shù)(Automatic Reference Counting
)。從OS X Lion和iOS 5開始,不再需要程序員手動(dòng)調(diào)用retain
和release
方法來(lái)管理Objective-C
對(duì)象的內(nèi)存,而是引入一種新的內(nèi)存管理機(jī)制Automatic Reference Counting(ARC)
,簡(jiǎn)單來(lái)說(shuō),它讓編譯器
來(lái)代替程序員來(lái)自動(dòng)加入retain
和release
方法來(lái)持有和放棄對(duì)象的所有權(quán)。
在ARC內(nèi)存管理機(jī)制中,id和其他對(duì)象類型變量必須是以下四個(gè)ownership qualifiers其中一個(gè)來(lái)修飾:
- __strong(默認(rèn),如果不指定其他,編譯器就默認(rèn)加入)
- __weak
- __unsafe_unretained
- __autoreleasing
比方說(shuō)下面這段程序
NSError *error;
BOOL OK = [myObject performOperationWithError:&error];
if (!OK) {
// Report the error.
// ...
開啟ARC經(jīng)過(guò)編譯器處理后將會(huì)變成下面這樣:
NSError * __strong error;
NSError * __autoreleasing tmp = error;
BOOL OK = [myObject performOperationWithError:&tmp];
error = tmp;
if (!OK) {
// Report the error.
// ...
接下來(lái)看幾個(gè)例子:
2.1 __strong ownership qualifier
如果變量var被__strong修飾,當(dāng)變量var指向某個(gè)對(duì)象objc,那么變量var持有某個(gè)對(duì)象objc的所有權(quán)
如果我想創(chuàng)建一個(gè)字符串,使用完之后將它釋放調(diào)用,使用MRC管理內(nèi)存的寫法應(yīng)該是這樣:
{
NSString *text = [[NSString alloc] initWithFormat:@"Hello, world"]; //@"Hello, world"對(duì)象的RC=1
NSLog(@"%@", text);
[text release]; //@"Hello, world"對(duì)象的RC=0
}
而如果是使用ARC方式的話,就text對(duì)象無(wú)需調(diào)用release
方法,而是當(dāng)text
變量超過(guò)作用域時(shí),編譯器來(lái)自動(dòng)加入[text release]
方法來(lái)釋放內(nèi)存
{
NSString *text = [[NSString alloc] initWithFormat:@"Hello, world"]; //@"Hello, world"對(duì)象的RC=1
NSLog(@"%@", text);
}
/*
* 當(dāng)text超過(guò)作用域時(shí),@"Hello, world"對(duì)象會(huì)自動(dòng)釋放,RC=0
*/
而當(dāng)你將text賦值給其他變量anotherText時(shí),MRC需要retain一下來(lái)持有所有權(quán),當(dāng)text和anotherText使用完之后,各個(gè)調(diào)用release方法來(lái)釋放。
{
NSString *text = [[NSString alloc] initWithFormat:@"Hello, world"]; //@"Hello, world"對(duì)象的RC=1
NSLog(@"%@", text);
NSString *anotherText = text; //@"Hello, world"對(duì)象的RC=1
[anotherText retain]; //@"Hello, world"對(duì)象的RC=2
NSLog(@"%@", anotherText);
[text release]; //@"Hello, world"對(duì)象的RC=1
[anotherText release]; //@"Hello, world"對(duì)象的RC=0
}
而使用ARC的話,并不需要調(diào)用retain和release方法來(lái)持有跟釋放對(duì)象。
{
NSString *text = [[NSString alloc] initWithFormat:@"Hello, world"]; //@"Hello, world"對(duì)象的RC=1
NSLog(@"%@", text);
NSString *anotherText = text; //@"Hello, world"對(duì)象的RC=2
NSLog(@"%@", anotherText);
}
/*
* 當(dāng)text和anotherText超過(guò)作用域時(shí),會(huì)自動(dòng)調(diào)用[text release]和[anotherText release]方法, @"Hello, world"對(duì)象的RC=0
*/
除了當(dāng)__strong
變量超過(guò)作用域時(shí),編譯器會(huì)自動(dòng)加入release
語(yǔ)句來(lái)釋放內(nèi)存,如果你將__strong
變量重新賦給它其他值,那么編譯器也會(huì)自動(dòng)加入release
語(yǔ)句來(lái)釋放變量指向之前的對(duì)象。例如:
{
NSString *text = [[NSString alloc] initWithFormat:@"Hello, world"]; //@"Hello, world"對(duì)象的RC=1
NSString *anotherText = text; //@"Hello, world"對(duì)象的RC=2
NSString *anotherText = [[NSString alloc] initWithFormat:@"Sam Lau"]; // 由于anotherText對(duì)象引用另一個(gè)對(duì)象@"Sam Lau",那么就會(huì)自動(dòng)調(diào)用[anotherText release]方法,使得@"Hello, world"對(duì)象的RC=1, @"Sam Lau"對(duì)象的RC=1
}
/*
* 當(dāng)text和anotherText超過(guò)作用域時(shí),會(huì)自動(dòng)調(diào)用[text release]和[anotherText release]方法,
* @"Hello, world"對(duì)象的RC=0和@"Sam Lau"對(duì)象的RC=0
*/
前面已經(jīng)提過(guò)內(nèi)存管理的四條規(guī)則:
Objective-C對(duì)象的動(dòng)作 | Objective-C對(duì)象的方法 |
---|---|
1. 創(chuàng)建一個(gè)對(duì)象并獲取它的所有權(quán) | alloc/new/copy/mutableCopy (RC = 1) |
2. 獲取對(duì)象的所有權(quán) | retain (RC + 1) |
3. 放棄對(duì)象的所有權(quán) | release (RC - 1) |
4. 釋放對(duì)象 | dealloc (RC = 0 ,此時(shí)會(huì)調(diào)用該方法) |
我們總結(jié)一下編譯器是按以下方法來(lái)實(shí)現(xiàn)的:
- 對(duì)于規(guī)則1和規(guī)則2,是通過(guò)
__strong
變量來(lái)實(shí)現(xiàn), - 對(duì)于規(guī)則3來(lái)說(shuō),當(dāng)變量超過(guò)它的作用域或被賦值或成員變量被丟棄時(shí)就能實(shí)現(xiàn)
- 對(duì)于規(guī)則4,當(dāng)
RC=0
時(shí),系統(tǒng)就會(huì)自動(dòng)調(diào)用
2.2 __weak ownership qualifier
其實(shí)編譯器根據(jù)__strong
修飾符來(lái)管理對(duì)象內(nèi)存。但是__strong
并不能解決引用循環(huán)(Reference Cycle)問題:對(duì)象A持有對(duì)象B,反過(guò)來(lái),對(duì)象B持有對(duì)象A;這樣會(huì)導(dǎo)致不能釋放內(nèi)存造成內(nèi)存泄露問題。
舉一個(gè)簡(jiǎn)單的例子,有一個(gè)類Test有個(gè)屬性objc,有兩個(gè)對(duì)象test1和test2的屬性objc互相引用test1和test2:
@interface Test : NSObject
@property (strong, nonatomic) id objc;
@end
{
Test *test1 = [Test new]; /* 對(duì)象a */
/* test1有一個(gè)強(qiáng)引用到對(duì)象a */
Test *test2 = [Test new]; /* 對(duì)象b */
/* test2有一個(gè)強(qiáng)引用到對(duì)象b */
test1.objc = test2; /* 對(duì)象a的成員變量objc有一個(gè)強(qiáng)引用到對(duì)象b */
test2.objc = test1; /* 對(duì)象b的成員變量objc有一個(gè)強(qiáng)引用到對(duì)象a */
}
/* 當(dāng)變量test1超過(guò)它作用域時(shí),它指向a對(duì)象會(huì)自動(dòng)release
* 當(dāng)變量test2超過(guò)它作用域時(shí),它指向b對(duì)象會(huì)自動(dòng)release
*
* 此時(shí),b對(duì)象的objc成員變量仍持有一個(gè)強(qiáng)引用到對(duì)象a
* 此時(shí),a對(duì)象的objc成員變量仍持有一個(gè)強(qiáng)引用到對(duì)象b
* 于是發(fā)生內(nèi)存泄露
*/
如何解決?于是我們引用一個(gè)__weakownership qualifier,被它修飾的變量都不持有對(duì)象的所有權(quán),而且當(dāng)變量指向的對(duì)象的RC為0時(shí),變量設(shè)置為nil。例如:
__weak NSString *text = [[NSString alloc] initWithFormat:@"Sam Lau"];
NSLog(@"%@", text);
由于text變量被__weak修飾,text并不持有@"Sam Lau"對(duì)象的所有權(quán),@"Sam Lau"對(duì)象一創(chuàng)建就馬上被釋放,并且編譯器給出警告??,所以打印結(jié)果為(null)。
所以,針對(duì)剛才的引用循環(huán)問題,只需要將Test類的屬性objc設(shè)置weak修飾符,那么就能解決。
@interface Test : NSObject
@property (weak, nonatomic) id objc;//修改成weak修飾符
@end
以及我們常用的block防止內(nèi)存泄漏也可以使用__weak 修飾符,引用官方給的例子如下:
MyViewController *myController = [[MyViewController alloc] init…];
// ...
MyViewController * __weak weakMyController = myController;
myController.completionHandler = ^(NSInteger result) {
MyViewController *strongMyController = weakMyController;
if (strongMyController) {
// ...
[strongMyController dismissViewControllerAnimated:YES completion:nil];
// ...
}
else {
// Probably nothing...
}
};
2.3 __unsafe_unretained ownership qualifier
__unsafe_unretained ownership qualifier
,正如名字所示,它是不安全的。它跟__weak
相似,被它修飾的變量都不持有對(duì)象的所有權(quán),但當(dāng)變量指向的對(duì)象的RC
為0時(shí),變量并不設(shè)置為ni
l,而是繼續(xù)保存對(duì)象的地址;這樣的話,對(duì)象有可能已經(jīng)釋放,但繼續(xù)訪問,就會(huì)造成非法訪問(Invalid Access)。例子如下:
__unsafe_unretained id obj0 = nil;
{
id obj1 = [[NSObject alloc] init]; // 對(duì)象A
/* 由于obj1是強(qiáng)引用,所以obj1持有對(duì)象A的所有權(quán),對(duì)象A的RC=1 */
obj0 = obj1;
/* 由于obj0是__unsafe_unretained,它不持有對(duì)象A的所有權(quán),但能夠引用它,對(duì)象A的RC=1 */
NSLog(@"A: %@", obj0);
}
/* 當(dāng)obj1超過(guò)它的作用域時(shí),它指向的對(duì)象A將會(huì)自動(dòng)釋放 */
NSLog(@"B: %@", obj0);
/* 由于obj0是__unsafe_unretained,當(dāng)它指向的對(duì)象RC=0時(shí),它會(huì)繼續(xù)保存對(duì)象的地址,所以兩個(gè)地址相同 */
打印結(jié)果是內(nèi)存地址相同:
如果將__unsafe_unretained改為weak的話,兩個(gè)打印結(jié)果將不同
__weak id obj0 = nil;
{
id obj1 = [[NSObject alloc] init]; // 對(duì)象A
/* 由于obj1是強(qiáng)引用,所以obj1持有對(duì)象A的所有權(quán),對(duì)象A的RC=1 */
obj0 = obj1;
/* 由于obj0是__unsafe_unretained,它不持有對(duì)象A的所有權(quán),但能夠引用它,對(duì)象A的RC=1 */
NSLog(@"A: %@", obj0);
}
/* 當(dāng)obj1超過(guò)它的作用域時(shí),它指向的對(duì)象A將會(huì)自動(dòng)釋放 */
NSLog(@"B: %@", obj0);
/* 由于obj0是__weak, 當(dāng)它指向的對(duì)象RC=0時(shí),它會(huì)自動(dòng)設(shè)置為nil,所以兩個(gè)打印結(jié)果將不同*/
2.4 __autoreleasing ownership qualifier
引入ARC之后,讓我們看看autorelease pool有哪些變化。沒有ARC之前的寫法如下:
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
// put object into pool
id obj = [[NSObject alloc] init];
[obj autorelease];
[pool drain];
/* 超過(guò)autorelease pool作用域范圍時(shí),obj會(huì)自動(dòng)調(diào)用release方法 */
引入ARC之后,寫法比之前更加簡(jiǎn)潔:
@autoreleasepool {
id __autoreleasing obj = [[NSObject alloc] init];
}
相比之前的創(chuàng)建、使用和釋放NSAutoreleasePool
對(duì)象,現(xiàn)在你只需要將代碼放在@autoreleasepool
塊即可。你也不需要調(diào)用autorelease
方法了,只需要用__autoreleasing
修飾變量即可。
但是我們很少或基本上不使用
autorelease pool
。當(dāng)我們使用XCode
創(chuàng)建工程后,有一個(gè)app
的入口文件main.m
使用了它:
int main(int argc, char * argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
2.4 Property(屬性)與ownership qualifier
有了ARC之后,新的property modifier也被引入到Objective-C類的property,例如:
@property (strong, nonatomic) NSString *text;
下面有張表來(lái)展示property modifier與ownership qualifier的對(duì)應(yīng)關(guān)系
Property modifier | Ownership qualifier |
---|---|
strong | __strong |
retain | __strong |
copy | __strong |
weak | __weak |
assign | __unsafe_unretained |
unsafe_unretained | __unsafe_unretained |
我們先看下當(dāng)我們命名一個(gè)屬性為retain 時(shí),然后調(diào)用@synthesize時(shí)編譯器將會(huì)做什么。引用官方原文如下:
setter方法:
- (void)setCount:(NSNumber *)newCount {
[newCount retain];
[_count release];
// Make the new assignment.
_count = newCount;
}
也就是這里會(huì)首先retain新值,然后釋放舊值(舊值RC -1),然后再賦值新值。
相同的當(dāng)命名一個(gè)屬性為copy類型時(shí)
setter方法:
- (void)setCount:(NSNumber *)newCount {
[_count release];
// Make the new assignment.
_count = [newCount copy];
}
retain與copy的區(qū)別在于,一個(gè)不產(chǎn)生新的對(duì)象只是對(duì)對(duì)象的RC + 1,另一個(gè)產(chǎn)生新的對(duì)象。
除了上面提到的屬性修飾符外還有atomic (default)
、nonatomic
、readonly
、readwrite
。
只能一個(gè)線程訪問,線程安全的(相當(dāng)于線程鎖的概念,一個(gè)時(shí)間段只有一個(gè)線程訪問不容易出錯(cuò),所以線程安全),低性能 等等
參考文章:
官方文檔Advanced Memory Management Programming Guide
官方文檔Transitioning to ARC Release Notes
官方文檔Memory Management Programming Guide for Core Foundation
官方文檔Transitioning to ARC Release Notes
iOS/OS X內(nèi)存管理(二):借助工具解決內(nèi)存問題