內(nèi)存管理是程序在運(yùn)行時(shí)分配內(nèi)存偏陪、使用內(nèi)存策菜,并在程序完成時(shí)釋放內(nèi)存的過程劣光。在Objective-C中亭引,也被看作是在眾多數(shù)據(jù)和代碼之間分配有限內(nèi)存資源的所有權(quán)(Ownership)的一種方式贞岭。
內(nèi)存管理關(guān)心的是清理或回收不用的內(nèi)存八毯,以便內(nèi)存能夠再次利用。如果一個(gè)對(duì)象不再使用瞄桨,就需要釋放對(duì)象占用的內(nèi)存话速。Objective-C提供了兩種內(nèi)存管理的方法:手動(dòng)管理內(nèi)存計(jì)數(shù)(MRR)和自動(dòng)引用計(jì)數(shù)(ARC)。這兩種方法都采用了一種稱為“引用計(jì)數(shù)”的模型來實(shí)現(xiàn)芯侥,該模型由Foundation框架的NSObject類和運(yùn)行時(shí)環(huán)境(Runtime Environment)共同提供泊交。下面,我們就先來介紹下什么是引用計(jì)數(shù)柱查。
1 引用計(jì)數(shù)
引用計(jì)數(shù)(Reference Count)是一個(gè)簡(jiǎn)單而有效的管理對(duì)象生命周期的方式廓俭,一般概念是:當(dāng)創(chuàng)建一個(gè)新的對(duì)象時(shí),初始的引用計(jì)數(shù)為1唉工。為保證對(duì)象的存在研乒,每當(dāng)創(chuàng)建一個(gè)引用到該對(duì)象時(shí),通過給對(duì)象發(fā)送retain
消息淋硝,為引用計(jì)數(shù)加1雹熬;當(dāng)不再需要對(duì)象時(shí)宽菜,通過給對(duì)象發(fā)送release
消息,為引用計(jì)數(shù)減1竿报;當(dāng)對(duì)象的引用計(jì)數(shù)為0時(shí)铅乡,系統(tǒng)就知道這個(gè)對(duì)象不再使用了,通過給對(duì)象發(fā)送dealloc
消息烈菌,銷毀對(duì)象并回收內(nèi)存阵幸。一般在retain
方法之后,引用計(jì)數(shù)通常也被稱為保留計(jì)數(shù)(retain count)芽世。
為了更好地闡述引用計(jì)數(shù)的機(jī)制挚赊,這里引用開關(guān)房間燈的例子來說明:
假設(shè)辦公室的照明設(shè)備只有一個(gè),進(jìn)入辦公室的人需要照明捂襟,離開辦公室的人不需要照明咬腕。因此,為保證辦公室僅有的照明設(shè)備得到很好的管理葬荷,只要辦公室還有人涨共,就會(huì)需要照明,燈就得開著宠漩,而當(dāng)辦公室沒人的時(shí)候举反,就需要關(guān)燈。為了判斷辦公室是否還有人扒吁,我們導(dǎo)入計(jì)數(shù)功能來計(jì)算“需要照明的人數(shù)”:
- 第一個(gè)進(jìn)入辦公室的人火鼻,需要照明,此時(shí)雕崩,“需要照明的人數(shù)”為1魁索,計(jì)數(shù)值從0變?yōu)?,需要開燈盼铁。
- 第二個(gè)進(jìn)入辦公室的人粗蔚,也需要照明,此時(shí)饶火,“需要照明的人數(shù)”為2鹏控,計(jì)數(shù)值從1變?yōu)?。
- 之后肤寝,每一個(gè)進(jìn)入辦公室的人都需要照明当辐,此時(shí)“需要照明的人數(shù)”依次增加,計(jì)數(shù)值也依次加1鲤看。
- 當(dāng)?shù)谝粋€(gè)人離開辦公室時(shí)缘揪,不再需要照明,此時(shí)“需要照明的人數(shù)”減1,計(jì)數(shù)值也減1寺晌。
- 之后世吨,只要有人離開辦公室澡刹,就不再需要照明呻征,此時(shí)“需要照明的人數(shù)”依次減少,計(jì)數(shù)值依次減1罢浇。
- 當(dāng)最后一個(gè)人離開辦公室時(shí),不再有人需要照明,“需要照明的人數(shù)”為0歹河,計(jì)數(shù)值減至0论寨,需要關(guān)燈。
在Objective-C中胞锰,對(duì)象就相當(dāng)于辦公室的照明設(shè)備灾锯,而對(duì)象的使用環(huán)境就相當(dāng)于進(jìn)入辦公室上班的人。其對(duì)應(yīng)關(guān)系可以用以下表格來表示:
進(jìn)入辦公室上班的人對(duì)照明設(shè)備所做的動(dòng)作 | 對(duì)象的使用環(huán)境對(duì)Objective-C對(duì)象所做的動(dòng)作 |
---|---|
開燈 | 生成對(duì)象 |
需要照明 | 持有對(duì)象 |
不再需要照明 | 釋放對(duì)象 |
關(guān)燈 | 銷毀對(duì)象 |
2 手動(dòng)管理內(nèi)存 MRR
手動(dòng)管理內(nèi)存嗅榕,即MRR(manual retain-release)顺饮,是基于引用計(jì)數(shù)來實(shí)現(xiàn)的,通過自己跟蹤對(duì)象來明確管理內(nèi)存凌那。它與ARC之間的唯一區(qū)別是:在MRR中兼雄,對(duì)象的保留和釋放都是由我們手動(dòng)處理,而在ARC中是自動(dòng)處理的帽蝶。
2.1 MRR內(nèi)存管理的基本原則
為了方便理解赦肋,我們先通過一個(gè)例子看下MRR中的內(nèi)存管理是如何工作的,之后會(huì)有總結(jié)励稳。
首先打開Xcode佃乘,創(chuàng)建一個(gè)新的項(xiàng)目(File\New\Project...),在這里我們將項(xiàng)目名稱寫為MemoryManagementDemo。為了確保我們的代碼是在MRR環(huán)境下驹尼,在進(jìn)行任何操作之前趣避,我們需要先檢查當(dāng)前項(xiàng)目是否啟用了ARC,如果是扶欣,將它關(guān)閉鹅巍,如下圖所示:
然后打開storyboard,刪除當(dāng)前視圖控制器料祠,從右下角的對(duì)象庫(kù)中找到Navigation Controller骆捧,并拖動(dòng)到畫布上。選中左邊的導(dǎo)航控制器髓绽,切換到屬性檢查器敛苇,勾選View Controller一欄下面的is Initial View Controller選項(xiàng),將該控制器作為初始控制器,完成后枫攀,該控制器左側(cè)有個(gè)白色的箭頭括饶,如下:
這時(shí)候如果你點(diǎn)擊運(yùn)行,會(huì)在模擬器中看到一個(gè)空的表視圖:
接下來我們刪除ViewController.h文件和ViewController.m文件来涨,并創(chuàng)建一個(gè)新的文件(File\New\File...)图焰,使它成為的UITableViewController的子類:
打開剛才創(chuàng)建的TableViewController.h文件,聲明一個(gè)數(shù)組類型的實(shí)例變量(這里我們使用下劃線_
作為實(shí)例變量名稱的起始字符蹦掐,你可以把它看成是實(shí)例變量的一部分):
#import <UIKit/UIKit.h>
@interface TableViewController : UITableViewController
{
NSArray *_titles;
}
@end
然后切換到TableViewController.m文件技羔,為了在表視圖中顯示內(nèi)容,我們?cè)?code>viewDidLoad方法中簡(jiǎn)單地設(shè)置數(shù)組:
- (void)viewDidLoad
{
[super viewDidLoad];
_titles = [[NSArray alloc] initWithObjects:@"1", @"2", @"3", @"4", @"5", @"6", @"7", @"8", @"9", @"10", nil];
}
在Objective-C中創(chuàng)建對(duì)象時(shí)卧抗,一般先向某個(gè)類發(fā)送alloc
消息來為新的對(duì)象分配內(nèi)存存儲(chǔ)空間藤滥,然后調(diào)用init
方法初始化對(duì)象。如果init
方法沒有使用任何參數(shù)社裆,可以用new
替代拙绊。即:
Class *object = [[Class alloc] init];
也可以寫成:
Class *object = [Class new];
在這里我們使用NSArray
類的initWithObjects:
方法創(chuàng)建數(shù)組對(duì)象,該方法返回一個(gè)新的對(duì)象泳秀,其中引用計(jì)數(shù)被設(shè)置為1标沪,所以當(dāng)我們不再使用對(duì)象時(shí),需要減少引用計(jì)數(shù)晶默,在OC中可以通過調(diào)用release
方法來實(shí)現(xiàn)谨娜。
但是應(yīng)該在哪里調(diào)用release
呢?前面說過磺陡,當(dāng)我們不再需要使用對(duì)象時(shí)趴梢,就可以調(diào)用該方法來釋放對(duì)象,因此我們將它寫在dealloc
方法中币他。
在viewDidLoad
下面創(chuàng)建一個(gè)dealloc
方法坞靶,并添加如下代碼:
- (void)dealloc
{
[_titles release];
_titles = nil;
[super dealloc];
}
我們不僅釋放了對(duì)象,還將對(duì)象設(shè)置為nil
蝴悉,這樣做可以避免很多問題彰阴。比如當(dāng)你嘗試在一個(gè)空的對(duì)象上調(diào)用一個(gè)方法,它什么都不做拍冠,但是如果你嘗試在一個(gè)被釋放掉的對(duì)象上調(diào)用方法尿这,會(huì)引起程序的崩潰。還有庆杜,在dealloc
方法的末尾記得調(diào)用super
射众,這樣做是為了使任何繼承來的對(duì)象都能夠被釋放掉,同時(shí)晃财,這也是使用手工內(nèi)存管理的另一麻煩之處叨橱,在釋放完自身的一些對(duì)象之后,都需要調(diào)用[super dealloc]
。最后罗洗,需要注意的是愉舔,永遠(yuǎn)不要直接對(duì)對(duì)象調(diào)用dealloc
方法。
接下來我們來完成tableView的dataSource部分:
#pragma mark - Table view data source
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return _titles.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell" forIndexPath:indexPath];
NSString *string = [_titles objectAtIndex:indexPath.row];
NSString *title = [[NSString alloc] initWithFormat:@"Title: %@", string];
cell.textLabel.text = title;
[title release];
return cell;
}
前兩個(gè)方法分別返回tableView的section數(shù)量和row數(shù)量伙菜,第三個(gè)方法用來設(shè)置每一行顯示的內(nèi)容轩缤,在這里我們顯示一個(gè)字符串。值的注意的是仇让,使用initWithFormat:
方法創(chuàng)建的字符串引用計(jì)數(shù)將為1典奉,結(jié)束時(shí)需要釋放躺翻,否則會(huì)造成內(nèi)存泄漏丧叽。另外,在tableView:cellForRowAtIndexPath:
方法中公你,我們使用dequeueReusableCellWithIdentifier:forIndexPath:
方法創(chuàng)建一個(gè)可重用的表視圖單元對(duì)象踊淳,這里指定的重用標(biāo)識(shí)符“cell”應(yīng)該與storyboard中的TableViewCell的標(biāo)識(shí)符一致。如果storyboard中的TableViewCell的標(biāo)識(shí)符為空陕靠,需要添加:
最后迂尝,打開storyboard,將視圖控制器設(shè)置為TableViewController:
點(diǎn)擊運(yùn)行剪芥,可以看到表視圖中的內(nèi)容:
現(xiàn)在我們已經(jīng)知道垄开,在手動(dòng)管理內(nèi)存的情況下,調(diào)用alloc/init
創(chuàng)建對(duì)象時(shí)税肪,引用計(jì)數(shù)為1溉躲,當(dāng)結(jié)束使用對(duì)象時(shí),需要調(diào)用release
方法使引用計(jì)數(shù)減為0益兄。但在有些情況下锻梳,比如在一個(gè)方法中不再需要用到這個(gè)對(duì)象,但需要將其返回净捅,這時(shí)候就引入了自動(dòng)釋放的概念疑枯,通過給對(duì)象發(fā)送autorelease
消息用以標(biāo)記該對(duì)象延遲釋放。
我們修改tableView:cellForRowAtIndexPath:
方法中的代碼:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell" forIndexPath:indexPath];
NSString *string = [_titles objectAtIndex:indexPath.row];
// 修改:
NSString *title = [[[NSString alloc] initWithFormat:@"Title: %@", string] autorelease];
cell.textLabel.text = title;
return cell;
}
上面的代碼刪除掉了[title release]
蛔六,并添加了autorelease
的調(diào)用荆永,這樣在創(chuàng)建title
對(duì)象后,會(huì)將對(duì)象添加到由自動(dòng)釋放池維護(hù)的對(duì)象列表中国章,我們可以繼續(xù)使用該對(duì)象來執(zhí)行此方法的其余部分具钥,直到自動(dòng)釋放池被清理時(shí)釋放對(duì)象。
接下來我們繼續(xù)修改上面的代碼:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell" forIndexPath:indexPath];
NSString *string = [_titles objectAtIndex:indexPath.row];
// 修改:
NSString *title = [NSString stringWithFormat:@"Title: %@", string];
cell.textLabel.text = title;
return cell;
}
這次我們使用stringWithFormat:
方法替換了alloc/init/autorelease
捉腥,該方法也會(huì)返回一個(gè)新的NSString
對(duì)象氓拼,引用計(jì)數(shù)為1,同時(shí)該對(duì)象也會(huì)被自動(dòng)添加到自動(dòng)釋放池中。你可能對(duì)此會(huì)感到困惑桃漾,同樣都是創(chuàng)建對(duì)象坏匪,為什么有些會(huì)自動(dòng)釋放有些需要調(diào)用autorelease
?這里有一個(gè)簡(jiǎn)單的約定可供參考:
- 如果使用以
init
或copy
開頭的方法創(chuàng)建對(duì)象撬统,返回的對(duì)象引用計(jì)數(shù)將為1适滓,并且不會(huì)自動(dòng)釋放。也就是說我們擁有該對(duì)象恋追,使用完成后需要調(diào)用release
釋放凭迹,或者調(diào)用autorelease
延遲釋放。 - 如果使用任何其它方式開頭的方法創(chuàng)建對(duì)象苦囱,返回的對(duì)象引用計(jì)數(shù)也將為1嗅绸,但會(huì)自動(dòng)釋放,使用完成后不需要再調(diào)用
release
或autorelease
撕彤。需要注意的是鱼鸠,以這種方式創(chuàng)建的對(duì)象,即使我們現(xiàn)在可以使用它羹铅,但是并不擁有該對(duì)象蚀狰。
現(xiàn)在我們又有一個(gè)新的問題:如果一個(gè)對(duì)象具有自動(dòng)釋放,那以后想要再次使用該對(duì)象要怎么辦职员?如果我們嘗試將該對(duì)象存儲(chǔ)在某個(gè)地方稍后使用的話麻蹋,可能會(huì)因?yàn)閲L試訪問已經(jīng)釋放的內(nèi)存而引起程序崩潰,因此我們調(diào)用retain
保留該對(duì)象焊切,使對(duì)象的引用計(jì)數(shù)變?yōu)?扮授,之后當(dāng)autorelease
觸發(fā)時(shí),引用計(jì)數(shù)減為1蛛蒙,并且對(duì)象不會(huì)被釋放糙箍。
下面打開TableViewController.h,我們繼續(xù)在文件中添加一個(gè)實(shí)例變量_lastTitleSelected
牵祟,當(dāng)選中某一行時(shí)深夯,該變量用于跟蹤上一次所選中行的對(duì)應(yīng)文本內(nèi)容:
@interface TableViewController : UITableViewController
{
NSArray *_titles;
NSString *_lastTitleSelected;
}
@end
然后在TableViewController.m文件底部添加tableView:didSelectRowAtIndexPath
方法及代碼:
#pragma mark - Table view delegate
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
// 創(chuàng)建消息
NSString *string = [_titles objectAtIndex:indexPath.row];
NSString *title = [NSString stringWithFormat:@"Title: %@", string];
NSString *message = [NSString stringWithFormat:@"Last title: %@. Current title: %@", _lastTitleSelected, title];
// 創(chuàng)建警報(bào)視圖以顯示彈出的消息
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"Hint" message:message preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction *action = [UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:nil];
[alert addAction:action];
[self presentViewController:alert animated:YES completion:nil];
// 釋放當(dāng)前實(shí)例變量
[_lastTitleSelected release];
// 重新設(shè)置該實(shí)例變量
_lastTitleSelected = [title retain];
}
該方法首先創(chuàng)建了一個(gè)message
對(duì)象,用作警報(bào)視圖彈出的消息內(nèi)容诺苹。然后創(chuàng)建一個(gè)警報(bào)控制器咕晋,當(dāng)選中tableView某一行時(shí)會(huì)彈出一個(gè)警報(bào)框。最后的兩句代碼是我們強(qiáng)調(diào)的重點(diǎn)收奔,由于警報(bào)框中的消息內(nèi)容包含上次選中行的文本信息和這次選中行的文本信息掌呜,而每次選中的行可能會(huì)都不一樣,因此為了只保留最近一次選中行的信息坪哄,我們需要先釋放當(dāng)前的實(shí)例變量_lastTitleSelected
质蕉,然后再重新設(shè)置該實(shí)例變量势篡。最后一行代碼中,調(diào)用了retain
來保留title
對(duì)象模暗,這是因?yàn)樵搶?duì)象在創(chuàng)建時(shí)具有自動(dòng)釋放禁悠,為防止在后面的使用中對(duì)象被釋放掉,我們使用retain
來增加對(duì)象的引用計(jì)數(shù)以確保程序不會(huì)崩潰兑宇。如果想測(cè)試碍侦,我們可以在方法中添加NSLog
語(yǔ)句輸出title
對(duì)象的保留計(jì)數(shù):
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
// 創(chuàng)建消息
...
NSString *title = [NSString stringWithFormat:@"Title: %@", string];
NSLog(@"retain count = %lu", (unsigned long)[title retainCount]);
...
// 重新設(shè)置該實(shí)例變量
_lastTitleSelected = [title retain];
NSLog(@"retain count = %lu", (unsigned long)[title retainCount]);
}
你將會(huì)看到下面的輸出結(jié)果:
retain count = 1
retain count = 2
最后,不要忘記在dealloc
方法中釋放實(shí)例變量_lastTitleSelected
:
- (void)dealloc
{
[_titles release];
_titles = nil;
[_lastTitleSelected release];
_lastTitleSelected = nil;
[super dealloc];
}
如果一切順利隶糕,點(diǎn)擊運(yùn)行瓷产,你會(huì)看到下面的運(yùn)行結(jié)果:
到這里,你對(duì)內(nèi)存管理應(yīng)該了解一些了枚驻,下面我們就來總結(jié)一下手動(dòng)管理內(nèi)存的規(guī)則:
以
alloc
濒旦、new
、copy
或mutableCopy
開頭的方法創(chuàng)建的對(duì)象测秸,我們擁有該對(duì)象疤估,使用完成后需要調(diào)用release
或autorelease
釋放。在
init
方法中為了獲取對(duì)象的所有權(quán)霎冯,或者在某些情況下避免對(duì)象被移除,可以使用retain
保留對(duì)象钞瀑。在使用完對(duì)象后沈撞,需要使用release
進(jìn)行釋放。對(duì)使用了
retain
雕什、copy
缠俺、mutableCopy
、alloc
或new
方法的任何對(duì)象贷岸,以及具有retain
和copy
特性的屬性進(jìn)行釋放壹士,需要重寫dealloc
方法,使得在對(duì)象被釋放的時(shí)候能夠釋放這些實(shí)例變量偿警。給對(duì)象發(fā)送
release
消息并不一定立即銷毀這個(gè)對(duì)象躏救,只有當(dāng)對(duì)象的引用計(jì)數(shù)減至0時(shí),對(duì)象才會(huì)被銷毀螟蒸,然后系統(tǒng)會(huì)發(fā)送dealloc
消息給這個(gè)對(duì)象用于釋放它的內(nèi)存盒使。如果在方法中不再需要用到某個(gè)對(duì)象,但需要將其返回七嫌,可以給該對(duì)象發(fā)送
autorelease
消息用以標(biāo)記延遲釋放少办,對(duì)象的引用計(jì)數(shù)會(huì)在當(dāng)前自動(dòng)釋放池的末尾減1。當(dāng)應(yīng)用程序終止時(shí)诵原,內(nèi)存中的所有對(duì)象都會(huì)被釋放英妓,不論它們是否在自動(dòng)釋放池中挽放。
當(dāng)不再需要一個(gè)對(duì)象時(shí),必須放棄所擁有的該對(duì)象的所有權(quán)蔓纠。
不能放棄一個(gè)你所不擁有的對(duì)象的所有權(quán)骂维。
2.2 自動(dòng)釋放池(autorelease pool)
上面有說到自動(dòng)釋放,在這里我們簡(jiǎn)單介紹下自動(dòng)釋放池贺纲。
自動(dòng)釋放池創(chuàng)建的目的就是希望可以幫助追蹤需要延遲一些時(shí)間釋放的對(duì)象航闺。
通過給對(duì)象發(fā)送autorelease
消息,就可以將一個(gè)對(duì)象添加到由自動(dòng)釋放池維護(hù)的對(duì)象列表中:
[object autorelease];
程序中使用來自Foundation猴誊、UIKit潦刃、AppKit框架的類時(shí),首先需要?jiǎng)?chuàng)建一個(gè)自動(dòng)釋放池懈叹,這樣來自這些框架的類才會(huì)創(chuàng)建并返回自動(dòng)釋放的對(duì)象乖杠,需要在程序中使用@autoreleasepool
指令(一般在項(xiàng)目中的main.m文件中就會(huì)看到該語(yǔ)句):
@autoreleasepool {
statements
}
當(dāng)執(zhí)行到autorelease塊的末尾時(shí),系統(tǒng)就會(huì)對(duì)池中的每個(gè)對(duì)象發(fā)送release
消息澄成,這將影響到所有發(fā)送過autorelease
消息并被添加到自動(dòng)釋放池中的對(duì)象胧洒,當(dāng)這些對(duì)象的引用計(jì)數(shù)減至0時(shí),會(huì)發(fā)送出dealloc
消息墨状,并且它們的內(nèi)存將會(huì)被釋放卫漫。
需要注意的是,自動(dòng)釋放池并不包含實(shí)際的對(duì)象肾砂,只是包含對(duì)象的引用列赎,對(duì)象將在自動(dòng)釋放池清理的時(shí)候被釋放。在ARC中镐确,自動(dòng)釋放池主要用于降低內(nèi)存峰值包吝,只是我們不再需要手動(dòng)添加autorelease
的代碼了。
2.3 使用訪問器方法簡(jiǎn)化內(nèi)存管理
手動(dòng)管理內(nèi)存時(shí)源葫,在代碼中使用retain
或release
來保留或釋放對(duì)象難免會(huì)出錯(cuò)诗越,為此,我們可以使用訪問器方法來減少內(nèi)存管理中的問題息堂。
通常所說的訪問器(accessor)方法指的是設(shè)值方法(setter)和取值方法(getter)嚷狞,又統(tǒng)稱為存取方法。設(shè)值方法即設(shè)置實(shí)例變量值的方法储矩,主要目的是將方法參數(shù)設(shè)為對(duì)應(yīng)的實(shí)例變量的值感耙,一般不會(huì)返回任何值。取值方法即檢索實(shí)例變量值的方法持隧,主要目的是獲取存儲(chǔ)在對(duì)象中的實(shí)例變量的值即硼,并通過程序返回發(fā)送出去,所以取值方法必須返回實(shí)例變量的值作為return的參數(shù)屡拨。
下面我們繼續(xù)延用之前的例子只酥,在TableViewController.h文件中為兩個(gè)實(shí)例變量添加設(shè)值方法和取值方法:
@interface TableViewController : UITableViewController
{
NSArray *_titles;
NSString *_lastTitleSelected;
}
- (void)setTitles:(NSArray *)titles;
- (NSArray *)titles;
- (void)setLastTitleSelected:(NSString *)lastTitleSelected;
- (NSString *)lastTitleSelected;
@end
然后在TableViewController.m文件的底部添加實(shí)現(xiàn)方法:
#pragma mark - Accessor method
- (void)setTitles:(NSArray *)titles
{
[titles retain];
[_titles release];
_titles = titles;
}
- (NSArray *)titles
{
return _titles;
}
- (void)setLastTitleSelected:(NSString *)lastTitleSelected
{
[lastTitleSelected retain];
[_lastTitleSelected release];
_lastTitleSelected = lastTitleSelected;
}
- (NSString *)lastTitleSelected
{
return _lastTitleSelected;
}
上面的getter方法容易理解褥实,它們只返回了實(shí)例變量。而在setter方法中裂允,我們首先使用retain
保留新傳入的參數(shù)變量以增加引用計(jì)數(shù)损离,然后使用release
釋放掉舊的實(shí)例變量以減少引用計(jì)數(shù),最后將實(shí)例變量的值設(shè)置為傳入的參數(shù)變量绝编。這樣僻澎,只要設(shè)置對(duì)象,就能保證在實(shí)例變量中存儲(chǔ)的對(duì)象有正確的引用計(jì)數(shù)十饥。另外窟勃,使用這樣的順序設(shè)置實(shí)例變量,可以防止將實(shí)例變量設(shè)置為同一對(duì)象的情況逗堵。還有秉氧,如果你仔細(xì)觀察上面的設(shè)值方法,你就會(huì)明白我們?cè)谧铋_始命名實(shí)例變量的時(shí)候要使用下劃線的原因蜒秤,最主要的是為了避免由實(shí)例變量名稱和參數(shù)變量名稱相同而引起的沖突汁咏。
接下來我們就使用這些存取方法來修改代碼中的實(shí)例變量。
首先我們使用設(shè)值方法來修改viewDidLoad
中的_titles
:
- (void)viewDidLoad
{
[super viewDidLoad];
[self setTitles:[[[NSArray alloc] initWithObjects:@"1", @"2", @"3", @"4", @"5", @"6", @"7", @"8", @"9", @"10", nil] autorelease]];
}
觀察仔細(xì)的話作媚,你會(huì)發(fā)現(xiàn)除了使用設(shè)值方法外攘滩,在末尾我們還調(diào)用了autorelease
。還記得在setter方法中我們使用retain
將傳入的參數(shù)變量引用計(jì)數(shù)加1嗎掂骏?這里我們使用了initWithObjects:
創(chuàng)建數(shù)組對(duì)象轰驳,會(huì)導(dǎo)致變量最終的引用計(jì)數(shù)為2,因此必須使用自動(dòng)釋放來減少引用計(jì)數(shù)弟灼。當(dāng)然,我們也可以使用arrayWithObjects:
方法創(chuàng)建數(shù)組對(duì)象冒黑,這樣就不需要再調(diào)用autorelease
了田绑。
另外,為了方便抡爹,Objective-C語(yǔ)言允許我們使用點(diǎn)運(yùn)算符.
代替方括號(hào)[]
來設(shè)置或獲取實(shí)例變量的值掩驱,即:
self.titles = xxx;
相當(dāng)于:
[self setTitles:xxx];
點(diǎn)運(yùn)算符
.
通常用于屬性,但不限于屬性冬竟。
下面我們使用點(diǎn)運(yùn)算符.
再次修改上面的方法:
- (void)viewDidLoad
{
[super viewDidLoad];
self.titles = [[[NSArray alloc] initWithObjects:@"1", @"2", @"3", @"4", @"5", @"6", @"7", @"8", @"9", @"10", nil] autorelease];
}
接下來在tableView:didSelectRowAtIndexPath
方法中修改_lastTitleSelected
:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
...
// 創(chuàng)建警報(bào)視圖以顯示彈出的消息
...
[self presentViewController:alert animated:YES completion:nil];
// 設(shè)置實(shí)例變量(刪除最后兩行代碼欧穴,用下面的代碼替代)
self.lastTitleSelected = title;
}
我們將實(shí)例變量的內(nèi)存管理的代碼都寫在了setter
方法中,所以在上面的代碼中設(shè)置實(shí)例變量就簡(jiǎn)單了很多泵殴。
另外需要說明的一點(diǎn)是涮帘,文檔中有提到:不要在初始化方法和dealloc方法中使用訪問器方法:
The only places you shouldn’t use accessor methods to set an instance variable are in initializer methods and
dealloc
.
如果要在init
方法中初始化一個(gè)對(duì)象,一般使用下面的形式:
- (instancetype)init
{
self = [super init];
if (self) {
instance = ...
}
return self;
}
在dealloc
方法中也要使用實(shí)例變量:
- (void)dealloc
{
[instance release];
[super dealloc];
}
之所以說不要在init
和dealloc
方法中使用訪問器方法笑诅,主要是由于面向?qū)ο蟮睦^承调缨、多態(tài)特性與訪問器方法可能造成的副作用聯(lián)合導(dǎo)致的疮鲫。繼承和多態(tài)導(dǎo)致在父類的實(shí)現(xiàn)中調(diào)用訪問器方法時(shí)可能會(huì)調(diào)用到子類重寫的存取方法,而此時(shí)子類部分并未完全初始化或已經(jīng)銷毀弦叶,導(dǎo)致混亂俊犯,從而出現(xiàn)一系列的邏輯問題甚至崩潰。但這種說法也不是絕對(duì)的伤哺,盡管存在風(fēng)險(xiǎn)但并不代表百分之百的崩潰或錯(cuò)誤燕侠,如果你在程序中有這樣寫,并且明確地知道它不會(huì)產(chǎn)生任何問題立莉,那么就可以使用它來簡(jiǎn)化你的代碼绢彤。比如,在這里我們修改dealloc
中的代碼:
- (void)dealloc
{
self.titles = nil;
self.lastTitleSelected = nil;
[super dealloc];
}
上面刪除了之前手動(dòng)調(diào)用release及將其設(shè)置為nil的兩行代碼桃序,然后使用setter方法替代≌认海現(xiàn)在我們將nil作為設(shè)值方法里的參數(shù)變量傳遞,即:
[nil retain];
[_titles release];
_titles = nil;
由于代碼中[nil retain]
不會(huì)執(zhí)行任何操作媒熊,因此我們?cè)?code>dealloc方法中使用self.titles = nil
不會(huì)產(chǎn)生任何問題奇适。不過為了安全起見,在不清楚是否會(huì)產(chǎn)生問題的情況下芦鳍,我們還是建議遵守文檔中的說明嚷往。
2.4 使用屬性
除了手動(dòng)編寫存取方法,Objective-C還提供了屬性(property)方便我們快速地為實(shí)例變量創(chuàng)建訪問器方法柠衅,并可選地實(shí)現(xiàn)它們皮仁。
屬性的聲明一般在接口部分,以@property
關(guān)鍵字開頭菲宴,后面可以選擇性的定義屬性的特性贷祈,然后以屬性的類型和名稱(一般情況下我們使用與實(shí)例變量相同的名稱,但并不是必須的)結(jié)尾喝峦。下面是幾種有效的屬性聲明:
@property int age;
@property (copy) NSString *name;
@property (nonatomic, strong) NSArray *array;
需要注意的是势誊,在聲明屬性時(shí),屬性的名稱前面不要以
new
谣蠢、alloc
粟耻、copy
或者init
這些詞語(yǔ)開頭。
聲明屬性的操作就相當(dāng)于聲明setter和getter方法眉踱,以上面的age
為例:
@property int age;
就相當(dāng)于:
- (void)setAge:(int)age;
- (int)age;
一般系統(tǒng)默認(rèn)的設(shè)值方法名稱是以set
開頭(setPropertyName)挤忙,默認(rèn)的取值方法是以屬性名稱命名(propertyName)。如果想要更改成自定義的名稱可以使用下面的方法:
- 用setter = setterName來指定setter方法的名稱谈喳,如:
@property (setter = setterName) int age;
- 用getter = getterName來指定getter方法的名稱册烈,如:
@property (getter = getterName) int age;
一般常用于BOOL
類型,getter方法通常以is
開頭叁执,比如標(biāo)識(shí)一個(gè)視圖是否隱藏的hidden
屬性茄厘,其getter方法應(yīng)該稱為isHidden
矮冬。可以這樣聲明:
@property (nonatomic,getter = isHidden ) BOOL hidden;
接下來我們?cè)陬惖膶?shí)現(xiàn)部分使用@synthesize
告訴編譯器自動(dòng)為屬性實(shí)現(xiàn)一個(gè)設(shè)值方法和取值方法次哈,即:
@implementation Class
@synthesize age;
@end
其中@synthesize age
默認(rèn)指定的實(shí)例變量名稱與屬性相同胎署,即:
@synthesize age = age;
Xcode 4.4以后,編譯器引入了屬性自動(dòng)合成(property autosynthesis)窑滞,也就是說編譯器會(huì)為每一個(gè)@property
添加@synthesize
琼牧,我們不需要再顯式地使用@synthesize
指令了。但需要注意的是哀卫,自動(dòng)合成默認(rèn)生成的實(shí)例變量名稱以_
為前綴巨坊,加上屬性名,即:
@synthesize age = _age;
如果不喜歡默認(rèn)的實(shí)例變量名稱此改,或者我們希望使用更有語(yǔ)義的名稱趾撵,就需要通過@synthesize
來指定等號(hào)后面的名稱作為我們希望的實(shí)例變量名:
@synthesize age = currentAge;
還需要說明的是,通常編譯器會(huì)自動(dòng)合成一個(gè)實(shí)例變量和至少一個(gè)訪問器方法共啃,如果我們?yōu)閹в?code>readwrite關(guān)鍵字的屬性同時(shí)手動(dòng)實(shí)現(xiàn)了setter和getter方法占调,或?yàn)閹в?code>readonly關(guān)鍵字的屬性實(shí)現(xiàn)了getter方法,那么編譯器會(huì)假定我們正在對(duì)屬性的實(shí)現(xiàn)進(jìn)行控制移剪,并且不會(huì)自動(dòng)合成實(shí)例變量究珊。在這種情況下,我們就需要手動(dòng)指定一個(gè)實(shí)例變量:
@synthesize property = _property;
另外需要注意的是纵苛,一般情況下使用@property
會(huì)在編譯期間自動(dòng)合成存取方法剿涮,但有些存取方法是在運(yùn)行時(shí)動(dòng)態(tài)創(chuàng)建的,這時(shí)聲明和使用屬性時(shí)會(huì)由于缺少方法而在編譯期間發(fā)出警告攻人,這時(shí)候我們可以使用@dynamic
語(yǔ)句來抑制警告:
@implementation Class
@dynamic age;
@end
下面我們就使用屬性替換我們代碼中的存取方法:
首先我們?cè)?strong>TableViewController.h文件中刪除實(shí)例變量和存取方法的聲明取试,使用屬性來代替:
@interface TableViewController : UITableViewController
@property (nonatomic, retain) NSArray *titles;
@property (nonatomic, retain) NSString *lastTitleSelected;
@end
然后切換到TableViewController.m文件,刪除之前寫的setter和getter方法怀吻,并在實(shí)現(xiàn)部分的最頂部添加@synthesize
:
@implementation TableViewController
@synthesize titles = _titles;
@synthesize lastTitleSelected = _lastTitleSelected;
當(dāng)然這里聲明的實(shí)例變量與默認(rèn)的一致想括,因此上面的@synthesize
也可以省略不寫。
現(xiàn)在可以運(yùn)行代碼試下烙博,你會(huì)發(fā)現(xiàn)模擬器跟以前一樣正常顯示。但是顯然這種讓編譯器自動(dòng)生成存取方法的做法比我們手動(dòng)編寫存取方法要簡(jiǎn)單很多烟逊,同時(shí)也更高效渣窜,并且在多核設(shè)備上可以使用多線程運(yùn)行。
2.5 屬性的特性
屬性的特性是使用一些特殊的關(guān)鍵字來告訴編譯器如何合成相關(guān)的訪問器方法宪躯。在我們的代碼中乔宿,聲明屬性時(shí)使用了nonatomic
和retain
。在此之前我們講到屬性的聲明時(shí)访雪,在示例中也使用到了copy
详瑞,但屬性的關(guān)鍵字遠(yuǎn)不止這幾個(gè)掂林, 一般分為線程相關(guān)、訪問器相關(guān)以及內(nèi)存相關(guān)這三類坝橡。下面是詳細(xì)介紹泻帮。
2.5.1 線程相關(guān):atomic、nonatomic
atomic(默認(rèn))
- 默認(rèn)特性计寇,如果沒有使用任何關(guān)鍵字指定屬性的特性锣杂,默認(rèn)情況下Objective-C屬性的特性是
atomic
。 -
atomic
表示原子屬性番宁,使用該屬性是為了告訴系統(tǒng)使用互斥(mutex)鎖定保護(hù)屬性的存取方法元莫,如果當(dāng)前進(jìn)程進(jìn)行到一半,其他線程來訪問當(dāng)前線程蝶押,可以保證先執(zhí)行完當(dāng)前線程踱蠢。也就是說,即使從不同的線程同時(shí)調(diào)用訪問器方法棋电,也能夠保證有一個(gè)值總能被getter方法完全檢索到或者被setter方法完全設(shè)置引矩。比如定罢,在多線程的程序中,如果有多個(gè)線程指向相同的實(shí)例變量,一個(gè)線程可以讀取甫煞,另一個(gè)線程可以寫入。當(dāng)它們?cè)谕粫r(shí)間點(diǎn)擊時(shí)暖释,讀取的線程將被保證能夠獲取到一個(gè)有效的值灵份,可能是更改前的值,也可能是更改后的值沉眶。 - 原子屬性與對(duì)象的線程安全性是不同的打却,并且使用原子屬性并不能保證線程安全。如果有多個(gè)線程同時(shí)訪問同一個(gè)實(shí)例變量谎倔,其中一個(gè)線程調(diào)用
release
柳击,就會(huì)造成程序崩潰。 - 使用
atomic
讓編譯器生成的互斥鎖定代碼會(huì)很耗費(fèi)資源片习,使程序變慢影響效率捌肴,所以一般很少使用。 -
atomic
屬性合成的訪問器方法是私有的藕咏,因此不能與自己實(shí)現(xiàn)的訪問器方法相結(jié)合状知。如果我們嘗試為atomic
屬性提供自定義的setter或getter方法,會(huì)收到編譯器的警告:
或者
nonatomic
-
nonatomic
與atomic
相反孽查,表示非原子屬性饥悴。使用該屬性是為了告訴系統(tǒng)不要使用互斥鎖定保護(hù)屬性的存取方法。當(dāng)有多個(gè)線程同時(shí)訪問同一個(gè)屬性時(shí)將會(huì)導(dǎo)致無法預(yù)計(jì)的結(jié)果。在這里不能保證調(diào)用訪問器方法時(shí)會(huì)返回一個(gè)有效的值西设。如果嘗試在寫入的中間讀取時(shí)瓣铣,我們可能會(huì)得到一個(gè)無效的值。 - 跟
atomic
比起來贷揽,nonatomic
效率要更高一些棠笑,如果要多次訪問一個(gè)屬性時(shí),使用nonatomic
會(huì)更高效擒滑。 - 一般在單線程和明確知道只有一個(gè)線程訪問的情況下廣泛使用腐晾。
- 使用
nonatomic
可以將自動(dòng)合成的setter或getter方法與自己手動(dòng)實(shí)現(xiàn)的getter或setter方法相結(jié)合。因此丐一,對(duì)于使用atomic
實(shí)現(xiàn)setter或getter時(shí)出現(xiàn)的警告藻糖,可以通過設(shè)置屬性的特性為nonatomic
來解除:
2.5.2 訪問器相關(guān):readonly、readwrite
readonly
-
readonly
表示只讀屬性库车,對(duì)包括自身在內(nèi)的所有類都是只讀的巨柒。 - 合成的訪問器只有g(shù)etter方法沒有setter方法。
readwrite(默認(rèn))
- 默認(rèn)特性柠衍,一般不需要顯式聲明洋满。
-
readwrite
表示讀寫屬性,即屬性允許被自身或其他類讀寫珍坊,與readonly
相反牺勾。 - 合成的訪問器同時(shí)擁有setter方法和getter方法。
如果希望一個(gè)屬性只允許自身讀寫阵漏,而對(duì)其他類都是只讀的驻民,可以在
.h
文件的接口部分中將屬性的特性聲明為readonly
,然后在.m
文件的私有接口部分再重新將屬性特性聲明為readwrite
即可履怯。
2.5.3 內(nèi)存相關(guān):retain回还、assign、strong叹洲、weak柠硕、copy、unsafe_unretained
retain
-
retain
表示屬性的保留操作运提,用于獲取對(duì)象的所有權(quán)蝗柔,會(huì)增加傳入對(duì)象的引用計(jì)數(shù)。 - 在屬性中使用
retain
關(guān)鍵字可以指示編譯器在設(shè)置實(shí)例變量之前保留傳入的變量民泵,在默認(rèn)的設(shè)值方法中會(huì)是:
if (_property != newValue) {
[_property release];
_property = [newValue retain];
}
- 主要在手動(dòng)內(nèi)存管理中使用诫咱,在ARC下,一般使用
strong
替代洪灯。
assign(默認(rèn))
- 默認(rèn)特性。一般像
int
、float
签钩、double
和NSInteger
掏呼、CGFloat
、BOOL
等值類型的屬性默認(rèn)使用assign
铅檩。 -
assign
用于屬性的賦值操作憎夷,不存在所有權(quán)關(guān)系。在默認(rèn)的設(shè)值方法中會(huì)是:
_property = newValue;
copy
-
copy
用于創(chuàng)建對(duì)象的副本昧旨,并且對(duì)該副本對(duì)象擁有所有權(quán)拾给,而非原對(duì)象本身。 - 在屬性中使用
copy
關(guān)鍵字可以指示編譯器在設(shè)置實(shí)例變量之前創(chuàng)建傳入變量的副本兔沃,在默認(rèn)的設(shè)值方法中會(huì)是:
if (_property != newValue) {
[_property release];
_property = [newValue copy];
}
-
copy
屬性對(duì)其創(chuàng)建的副本對(duì)象隱式強(qiáng)引用蒋得。同時(shí)也意味著該屬性將使用strong
,因?yàn)樗仨毐3制鋭?chuàng)建的副本對(duì)象乒疏。 - 為
copy
屬性設(shè)置的任何對(duì)象必須遵守NSCopying
協(xié)議额衙。 - 如果需要直接設(shè)置
copy
屬性的實(shí)例變量,例如在初始化方法中怕吴,要記得設(shè)置原始對(duì)象的副本窍侧。
strong(默認(rèn))
- 默認(rèn)特性。一般Objective-C對(duì)象的屬性默認(rèn)是
strong
转绷。 -
strong
是在引入ARC的時(shí)候引入的關(guān)鍵字伟件,相當(dāng)于MRR下的retain
。 - 使用
strong
聲明強(qiáng)引用议经,表示實(shí)例變量對(duì)傳入的對(duì)象擁有所有權(quán)斧账,只要持有該屬性中對(duì)象的引用,該對(duì)象就不會(huì)被釋放爸业。如果有兩個(gè)強(qiáng)引用的對(duì)象相互指向?qū)Ψ狡浣荆蜁?huì)造成強(qiáng)引用循環(huán)。 - 需要區(qū)分的是扯旷,在Objective-C中拯爽,對(duì)象屬性默認(rèn)是
strong
,而對(duì)象變量默認(rèn)是__strong
钧忽。
weak
-
weak
也是ARC下的屬性關(guān)鍵字毯炮,但它是從iOS 5引入,因此在iOS 5之前不可用耸黑。 - 使用
weak
聲明弱引用桃煎,與strong
相反,它表示實(shí)例變量對(duì)傳入的對(duì)象沒有所有權(quán)大刊,并且在setter方法中也不會(huì)對(duì)傳入對(duì)象增加引用計(jì)數(shù)为迈。當(dāng)對(duì)象被釋放后,實(shí)例變量會(huì)自動(dòng)設(shè)置為nil。 - 對(duì)于變量來說葫辐,我們可以使用
__weak
將變量聲明為弱指針變量搜锰,并且在實(shí)際應(yīng)用中,我們也通過使用__weak
將強(qiáng)引用替換為弱引用耿战,以此來解決強(qiáng)引用循環(huán)的問題:
NSObject * __weak weakVariable;
- 一般情況下蛋叼,delegate和outlet用
weak
來聲明。
unsafe_unretained
-
unsafe_unretained
表示不安全的引用剂陡,是iOS 5之前替代weak
的關(guān)鍵字狈涮。 - 與
weak
不同的是,使用unsafe_unretained
聲明的屬性鸭栖,當(dāng)對(duì)象被釋放后歌馍,實(shí)例變量不會(huì)自動(dòng)設(shè)置為nil。這意味著我們將留下一個(gè)懸掛指針纤泵,指向原先被釋放的對(duì)象所占用的內(nèi)存骆姐,會(huì)導(dǎo)致程序崩潰,因此它被稱為是“不安全的”捏题。
2.6 內(nèi)存管理要避免的問題
內(nèi)存管理不正確導(dǎo)致的主要問題有兩種:
釋放或重寫仍在使用的內(nèi)存導(dǎo)致內(nèi)存損壞:通常會(huì)導(dǎo)致應(yīng)用程序崩潰玻褪,甚至導(dǎo)致用戶數(shù)據(jù)遭到改寫或損壞。
沒有釋放不再使用的內(nèi)存導(dǎo)致內(nèi)存泄漏:會(huì)導(dǎo)致應(yīng)用程序?qū)?nèi)存的使用不斷增加公荧,從而導(dǎo)致系統(tǒng)性能下降或應(yīng)用程序被終止带射。
對(duì)于上面第一種問題,我們可以使用NSZombieEnabled調(diào)試工具來查找過度釋放的對(duì)象循狰。對(duì)于第二種問題窟社,可以使用Instruments跟蹤引用計(jì)數(shù)事件并查找內(nèi)存泄漏。
如果想在編譯時(shí)識(shí)別出代碼中的問題绪钥,可以使用Xcode中內(nèi)置的靜態(tài)分析功能灿里。這將使XCode運(yùn)行我們的代碼,并查找可以自動(dòng)檢測(cè)到的任何錯(cuò)誤程腹,以警告我們有任何潛在的問題匣吊。下面我們就對(duì)之前的項(xiàng)目使用該功能來檢測(cè)下是否存在問題:
打開項(xiàng)目,在頂部的菜單欄選擇Product\Analyze:
分析完成后寸潦,你會(huì)看到一個(gè)藍(lán)色的小方塊標(biāo)記色鸳,點(diǎn)擊之后會(huì)在左側(cè)看到詳細(xì)信息:
上面的消息告訴我們檢測(cè)到內(nèi)存泄漏,實(shí)例變量_window
需要被釋放见转,缺少dealloc
方法命雀,所以我們?cè)?code>AppDelegate.m的實(shí)現(xiàn)部分添加dealloc
方法,并對(duì)實(shí)例變量_window
調(diào)用release
方法:
- (void)dealloc
{
[_window release];
_window = nil;
[super dealloc];
}
再次點(diǎn)擊菜單欄中的Product\Analyze斩箫,你會(huì)看到之前的標(biāo)記消失了吏砂,同時(shí)也沒有檢測(cè)到其他新問題撵儿。
3 自動(dòng)引用計(jì)數(shù) ARC
自動(dòng)引用計(jì)數(shù),即ARC(Automatic Reference Counting)赊抖,是Xcode 4.2版本的一個(gè)新特性统倒,使用了與手動(dòng)管理相同的引用計(jì)數(shù)系統(tǒng),不同的是氛雪,系統(tǒng)在編譯時(shí)會(huì)幫我們插入合適的內(nèi)存管理方法,保留和釋放都是自動(dòng)處理的耸成,從而避免了手動(dòng)引用計(jì)數(shù)的一些潛在陷阱报亩。一般在新項(xiàng)目中被推薦使用。
3.1 ARC下內(nèi)存管理的規(guī)則
ARC的規(guī)則很簡(jiǎn)單井氢,我們不需要再手動(dòng)保留和釋放對(duì)象弦追,需要做的只是管理指向?qū)ο蟮闹羔槨V灰兄羔樦赶蛟搶?duì)象花竞,該對(duì)象將保留在內(nèi)存中劲件;當(dāng)指針指向其他對(duì)象,或不存在時(shí)约急,該對(duì)象將被自動(dòng)釋放零远。
下面我們列出在ARC下內(nèi)存管理的規(guī)則:
- 不能顯式調(diào)用
dealloc
,或retain
厌蔽、release
牵辣、retainCount
、autorelease
等奴饮,甚至也不能使用@selector(retain)
纬向、@selector(release)
等。 - 如果需要管理除釋放實(shí)例變量之外的資源戴卜,則可以實(shí)現(xiàn)
dealloc
方法逾条,并且自定義的dealloc
方法不需要調(diào)用[super dealloc]
。 - 訪問器方法的名稱不能以
new
開頭投剥,這反過來也意味著不能聲明一個(gè)以new
開頭的屬性师脂,除非我們指定了一個(gè)不同的getter:
//錯(cuò)誤:
@property NSString * newTitle;
//正確:
@property(getter = theNewTitle)NSString * newTitle;
3.2 MRR轉(zhuǎn)化為ARC
在上面的demo中我們都是用MRR來進(jìn)行內(nèi)存管理的,現(xiàn)在我們需要把它轉(zhuǎn)化成ARC薇缅,最容易想到的方法是手動(dòng)轉(zhuǎn)換危彩,需要把所有調(diào)用到retain
、release
等方法的代碼都去掉泳桦,但這樣做會(huì)很麻煩汤徽。幸運(yùn)的是,Xcode提供了一個(gè)ARC自動(dòng)轉(zhuǎn)換工具灸撰,可以幫助我們方便地將源碼轉(zhuǎn)為ARC谒府,更靈活的是拼坎,它不但可以將項(xiàng)目中的所有文件轉(zhuǎn)換為ARC,還可以選擇性地對(duì)指定的文件進(jìn)行轉(zhuǎn)換完疫,對(duì)于一些不想轉(zhuǎn)換的文件可以禁用ARC泰鸡。
下面我們就使用這種自動(dòng)轉(zhuǎn)換工具轉(zhuǎn)換我們的代碼:
首先,ARC是LLVM3.0編譯器的特性壳鹤,因此我們先來確認(rèn)下當(dāng)前的編譯器是否符合盛龄。選中文件中的項(xiàng)目,在Build Settings搜索框中輸入“compiler”芳誓,然后在Build Options中查看第一項(xiàng)Compiler for C/C++/Objective-C對(duì)應(yīng)的編譯器版本:
在轉(zhuǎn)換前我們先點(diǎn)擊Xcode菜單欄中的Product\Build以確保當(dāng)前代碼沒有問題余舶。
接下來從Xcode的菜單欄中選擇Edit\Convert\To Objective-C ARC...:
然后在彈出的窗口中點(diǎn)擊第一個(gè)圖標(biāo)下的小三角可以展開所有文件,在這里锹淌,為了對(duì)比手動(dòng)轉(zhuǎn)換匿值,我們?nèi)∠x中AppDelegate.m文件,只選中其他兩個(gè)默認(rèn)勾選的文件進(jìn)行轉(zhuǎn)換赂摆,然后點(diǎn)擊check:
繼續(xù)在彈出的窗口點(diǎn)擊Next:
你會(huì)看到正在生成轉(zhuǎn)化:
轉(zhuǎn)換完成后將會(huì)顯示所有文件的預(yù)覽挟憔。左側(cè)窗格顯示已更改的文件,右側(cè)窗格顯示原文件烟号。這里顯示的是TableViewController.h文件绊谭,你會(huì)看到在屬性聲明中使用strong
替代了retain
。
接下來我們切換到TableViewController.m文件:
這里一共有兩處更改褥符。首先是在viewDidLoad方法中龙誊,初始化titles
時(shí)刪除了autorelease
的調(diào)用。然后刪除了dealloc
方法及內(nèi)容喷楣。
確認(rèn)后趟大,繼續(xù)點(diǎn)擊Save保存更改,就可以在我們的項(xiàng)目中看到之前預(yù)覽文件的更改铣焊,轉(zhuǎn)換完成逊朽。
再次編譯程序,點(diǎn)擊Product\Build曲伊,顯示編譯成功叽讳。
還有一點(diǎn)需要知道的是,在同一個(gè)項(xiàng)目中將ARC代碼與非ARC代碼相結(jié)合是可行的坟募。下面我們打開AppDelegate.m文件岛蚤,會(huì)看到該文件依然存在dealloc
方法,并且可以正常運(yùn)行懈糯,這是因?yàn)槲覀內(nèi)∠催x該文件時(shí)涤妒,轉(zhuǎn)換工具已經(jīng)禁用這兩個(gè)源文件的ARC,我們可以在TARGETS的Build Phases中看到:
AppDelegate.m文件后面被加上了-fno-objc-arc
的編譯標(biāo)記赚哗,表示該文件將不使用ARC規(guī)則進(jìn)行編譯她紫。相反地硅堆,如果想對(duì)特定的文件啟用ARC,可以為其添加-fobjc-arc
標(biāo)記贿讹。在這里渐逃,我們雙擊AppDelegate.m文件后面的的標(biāo)記,將其更改為-fobjc-arc
以對(duì)該文件啟用ARC:
再次點(diǎn)擊Product\Build編譯程序民褂,會(huì)看到錯(cuò)誤提示茄菊,下面我們就手動(dòng)更改代碼來修復(fù)這些錯(cuò)誤:
首先打開AppDelegate.m文件,看到錯(cuò)誤主要發(fā)生在dealloc
方法中:
從錯(cuò)誤信息中我們不難看出赊堪,主要是由于在ARC下調(diào)用release
和dealloc
導(dǎo)致的买羞。我們刪除整個(gè)dealloc
方法,錯(cuò)誤消失雹食,再次點(diǎn)擊Product\Build編譯程序,編譯成功期丰。此時(shí)群叶,運(yùn)行程序,會(huì)發(fā)現(xiàn)一切正常顯示钝荡。
至此街立,我們的轉(zhuǎn)換工作已經(jīng)全部完成,項(xiàng)目中的所有文件都使用了ARC埠通。如果對(duì)其中的代碼還有問題赎离,可以下載MemoryManagementDemo查看。
4 Core Foundation 對(duì)象的內(nèi)存管理
4.1 Core Foundation
Core Foundation是基于Objective-C的Foundation框架端辱,但是以C語(yǔ)言實(shí)現(xiàn)梁剔。對(duì)于大多數(shù)應(yīng)用程序,我們并不需要使用Core Foundation舞蔽,一般從Objective-C中就可以完成任何我們想要的操作荣病。然而,對(duì)于一些底層的API渗柿,比如Core Graphics和Core Text等个盆,就需要我們對(duì)Core Foundation有所了解。
一般底層的Core Foundation對(duì)象大多以CreateWith
開頭的函數(shù)來創(chuàng)建朵栖,其內(nèi)存管理只需要延續(xù)手工引用計(jì)數(shù)的辦法即可颊亮。對(duì)于引用計(jì)數(shù)的修改,需要使用CFRetain
和CFRelease
函數(shù)陨溅,其功能與Objective-C對(duì)象的retain
和release
方法類似终惑。
// 創(chuàng)建一個(gè)CFStringRef對(duì)象
CFStringRef cfString = CFStringCreateWithCString(kCFAllocatorDefault, "string", kCFStringEncodingUTF8);
// 保留該對(duì)象,引用計(jì)數(shù)加1
CFRetain(cfString);
// 釋放該對(duì)象声登,引用計(jì)數(shù)減1
CFRelease(cfString);
4.2 免費(fèi)橋接(toll-free bridged)
Core Foundation框架和Foundation框架中有許多數(shù)據(jù)類型可以互換使用狠鸳,比如我們可以使用NSString
對(duì)象將其用作CFStringRef
揣苏,也可以使用CFStringRef
對(duì)象將其用作NSString
。這種可以互換使用的數(shù)據(jù)類型也被稱為免費(fèi)橋接數(shù)據(jù)類型件舵。這意味著我們可以使用相同的數(shù)據(jù)結(jié)構(gòu)作為Core Foundation函數(shù)調(diào)用的參數(shù)或者Objective-C消息調(diào)用的接收者卸察。然而,并不是所有的數(shù)據(jù)類型都是免費(fèi)橋接的铅祸,詳細(xì)列表可以參考Toll-Free Bridged Types坑质。
在免費(fèi)橋接中,與內(nèi)存管理相關(guān)的一個(gè)重要問題就是轉(zhuǎn)換過程中對(duì)象的所有權(quán)問題临梗。比如在ARC下涡扼,我們需要將一個(gè)Core Foundation對(duì)象轉(zhuǎn)換成一個(gè)Objective-C對(duì)象,這時(shí)候就需要告訴編譯器如何管理對(duì)象的所有權(quán)盟庞。于是我們引入bridge相關(guān)的關(guān)鍵字來說明對(duì)象的所有權(quán)語(yǔ)義:
4.2.1 __bridge
使用__bridge
可以在Objective-C對(duì)象和Core Foundation對(duì)象之間相互轉(zhuǎn)換吃沪,此轉(zhuǎn)換只做類型轉(zhuǎn)換,不轉(zhuǎn)移對(duì)象的所有權(quán)什猖。
- 使用
__bridge
將Objective-C對(duì)象轉(zhuǎn)換為Core Foundation對(duì)象票彪,使用完成后由ARC負(fù)責(zé)釋放對(duì)象:
// 創(chuàng)建一個(gè)NSString對(duì)象
NSString *nsString = @"string";
// 將NSString對(duì)象轉(zhuǎn)換為CFStringRef對(duì)象
CFStringRef cfString = (__bridge CFStringRef)nsString;
- 使用
__bridge
將Core Foundation對(duì)象轉(zhuǎn)換為Objective-C對(duì)象,完成后需要調(diào)用CFRelease
釋放對(duì)象:
// 創(chuàng)建一個(gè)CFStringRef對(duì)象
CFStringRef cfString = CFStringCreateWithCString(kCFAllocatorDefault, "string", kCFStringEncodingUTF8);
// 將CFStringRef對(duì)象轉(zhuǎn)換為NSString對(duì)象
NSString *nsString = (__bridge NSString*)cfString;
// 釋放CFStringRef對(duì)象
CFRelease(cfString);
4.2.2 __bridge_retained
使用__bridge_retained
或CFBridgingRetain
將Objective-C對(duì)象轉(zhuǎn)換為Core Foundation對(duì)象不狮,此轉(zhuǎn)換會(huì)將Objective-C對(duì)象的所有權(quán)轉(zhuǎn)移給Core Foundation對(duì)象降铸,使用完成后需要調(diào)用CFRelease
釋放對(duì)象所有權(quán)。
// 創(chuàng)建一個(gè)NSString對(duì)象
NSString *nsString = @"string";
// 將NSString對(duì)象轉(zhuǎn)換為CFStringRef對(duì)象
CFStringRef cfString = (__bridge_retained CFStringRef)nsString;
// 釋放CFStringRef對(duì)象
CFRelease(cfString);
4.2.3 __bridge_transfer
使用__bridge_transfer
或CFBridgingRelease
將Core Foundation對(duì)象轉(zhuǎn)換為Objective-C對(duì)象摇零,此轉(zhuǎn)換會(huì)將Core Foundation對(duì)象的所有權(quán)轉(zhuǎn)移給ARC推掸,使用完成后由ARC負(fù)責(zé)釋放對(duì)象所有權(quán)。
// 創(chuàng)建一個(gè)CFStringRef對(duì)象
CFStringRef cfString = CFStringCreateWithCString(kCFAllocatorDefault, "string", kCFStringEncodingUTF8);
// 將CFStringRef對(duì)象轉(zhuǎn)換為NSString對(duì)象
NSString *nsString = (__bridge_transfer NSString*)cfString;
5 參考資料
Advanced Memory Management Programming Guide
Transitioning to ARC Release Notes
Memory Management Tutorial for iOS
Beginning ARC in iOS 5 Tutorial