Objective-C中的內(nèi)存管理

內(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)芽世。

referenceCount.png

為了更好地闡述引用計(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)閉鹅巍,如下圖所示:

closeARC.png

然后打開storyboard,刪除當(dāng)前視圖控制器料祠,從右下角的對(duì)象庫(kù)中找到Navigation Controller骆捧,并拖動(dòng)到畫布上。選中左邊的導(dǎo)航控制器髓绽,切換到屬性檢查器敛苇,勾選View Controller一欄下面的is Initial View Controller選項(xiàng),將該控制器作為初始控制器,完成后枫攀,該控制器左側(cè)有個(gè)白色的箭頭括饶,如下:

addNavigationController.png

這時(shí)候如果你點(diǎn)擊運(yùn)行,會(huì)在模擬器中看到一個(gè)空的表視圖:

emptyTableView.png

接下來我們刪除ViewController.h文件和ViewController.m文件来涨,并創(chuàng)建一個(gè)新的文件(File\New\File...)图焰,使它成為的UITableViewController的子類:

createNewFile.png

打開剛才創(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í)符為空陕靠,需要添加:

addCellIdentifier.png

最后迂尝,打開storyboard,將視圖控制器設(shè)置為TableViewController

setController.png

點(diǎn)擊運(yùn)行剪芥,可以看到表視圖中的內(nèi)容:

tableView.png

現(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)單的約定可供參考:

  • 如果使用以initcopy開頭的方法創(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)用releaseautorelease撕彤。需要注意的是鱼鸠,以這種方式創(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é)果:

didSelectRow.png

到這里,你對(duì)內(nèi)存管理應(yīng)該了解一些了枚驻,下面我們就來總結(jié)一下手動(dòng)管理內(nèi)存的規(guī)則:

  • alloc濒旦、newcopymutableCopy開頭的方法創(chuàng)建的對(duì)象测秸,我們擁有該對(duì)象疤估,使用完成后需要調(diào)用releaseautorelease釋放。

  • init方法中為了獲取對(duì)象的所有權(quán)霎冯,或者在某些情況下避免對(duì)象被移除,可以使用retain保留對(duì)象钞瀑。在使用完對(duì)象后沈撞,需要使用release進(jìn)行釋放。

  • 對(duì)使用了retain雕什、copy缠俺、mutableCopyallocnew方法的任何對(duì)象贷岸,以及具有retaincopy特性的屬性進(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í)源葫,在代碼中使用retainrelease來保留或釋放對(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];
}

之所以說不要在initdealloc方法中使用訪問器方法笑诅,主要是由于面向?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í)使用了nonatomicretain。在此之前我們講到屬性的聲明時(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ì)收到編譯器的警告:
atomicSetterWarning.png

或者

atomicGetterWarning.png

nonatomic

  • nonatomicatomic相反孽查,表示非原子屬性饥悴。使用該屬性是為了告訴系統(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來解除:
nonatomicFixSetterWarning.png
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)特性。一般像intfloat签钩、doubleNSInteger掏呼、CGFloatBOOL等值類型的屬性默認(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

useAnalyze.png

分析完成后寸潦,你會(huì)看到一個(gè)藍(lán)色的小方塊標(biāo)記色鸳,點(diǎn)擊之后會(huì)在左側(cè)看到詳細(xì)信息:

momoryLeak.png

上面的消息告訴我們檢測(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牵辣、retainCountautorelease等奴饮,甚至也不能使用@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)用到retainrelease等方法的代碼都去掉泳桦,但這樣做會(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)的編譯器版本:

checkCompiler.png

在轉(zhuǎn)換前我們先點(diǎn)擊Xcode菜單欄中的Product\Build以確保當(dāng)前代碼沒有問題余舶。

接下來從Xcode的菜單欄中選擇Edit\Convert\To Objective-C ARC...

convertToARC.png

然后在彈出的窗口中點(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

selectFiles.png

繼續(xù)在彈出的窗口點(diǎn)擊Next

clickNext.png

你會(huì)看到正在生成轉(zhuǎn)化:

generatingPreview.png

轉(zhuǎn)換完成后將會(huì)顯示所有文件的預(yù)覽挟憔。左側(cè)窗格顯示已更改的文件,右側(cè)窗格顯示原文件烟号。這里顯示的是TableViewController.h文件绊谭,你會(huì)看到在屬性聲明中使用strong替代了retain

previewTableViewController.h.png

接下來我們切換到TableViewController.m文件:

previewTableViewController.m.png

這里一共有兩處更改褥符。首先是在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,我們可以在TARGETSBuild Phases中看到:

checkCompilerFlags.png

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:

changeCompilerFlags.png

再次點(diǎn)擊Product\Build編譯程序民褂,會(huì)看到錯(cuò)誤提示茄菊,下面我們就手動(dòng)更改代碼來修復(fù)這些錯(cuò)誤:

首先打開AppDelegate.m文件,看到錯(cuò)誤主要發(fā)生在dealloc方法中:

errorsMessage.png

從錯(cuò)誤信息中我們不難看出赊堪,主要是由于在ARC下調(diào)用releasedealloc導(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ù)的修改,需要使用CFRetainCFRelease函數(shù)陨溅,其功能與Objective-C對(duì)象的retainrelease方法類似终惑。

    // 創(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_retainedCFBridgingRetain將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_transferCFBridgingRelease將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

Encapsulating Data

Transitioning to ARC Release Notes

Obj-C Memory Management

Memory Management Tutorial for iOS

Properties Tutorial for iOS

Beginning ARC in iOS 5 Tutorial

iOS核心技術(shù)之:內(nèi)存管理之二手動(dòng)內(nèi)存管理

理解 iOS 的內(nèi)存管理

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末驻仅,一起剝皮案震驚了整個(gè)濱河市谅畅,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌雾家,老刑警劉巖铃彰,帶你破解...
    沈念sama閱讀 206,311評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異芯咧,居然都是意外死亡牙捉,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門敬飒,熙熙樓的掌柜王于貴愁眉苦臉地迎上來邪铲,“玉大人,你說我怎么就攤上這事无拗〈剑” “怎么了?”我有些...
    開封第一講書人閱讀 152,671評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵英染,是天一觀的道長(zhǎng)揽惹。 經(jīng)常有香客問我被饿,道長(zhǎng),這世上最難降的妖魔是什么搪搏? 我笑而不...
    開封第一講書人閱讀 55,252評(píng)論 1 279
  • 正文 為了忘掉前任狭握,我火速辦了婚禮,結(jié)果婚禮上疯溺,老公的妹妹穿的比我還像新娘论颅。我一直安慰自己,他們只是感情好囱嫩,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,253評(píng)論 5 371
  • 文/花漫 我一把揭開白布恃疯。 她就那樣靜靜地躺著,像睡著了一般墨闲。 火紅的嫁衣襯著肌膚如雪今妄。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,031評(píng)論 1 285
  • 那天鸳碧,我揣著相機(jī)與錄音蛙奖,去河邊找鬼。 笑死杆兵,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的仔夺。 我是一名探鬼主播琐脏,決...
    沈念sama閱讀 38,340評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼缸兔!你這毒婦竟也來了日裙?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,973評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤惰蜜,失蹤者是張志新(化名)和其女友劉穎昂拂,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體抛猖,經(jīng)...
    沈念sama閱讀 43,466評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡格侯,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,937評(píng)論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了财著。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片联四。...
    茶點(diǎn)故事閱讀 38,039評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖撑教,靈堂內(nèi)的尸體忽然破棺而出朝墩,到底是詐尸還是另有隱情,我是刑警寧澤伟姐,帶...
    沈念sama閱讀 33,701評(píng)論 4 323
  • 正文 年R本政府宣布收苏,位于F島的核電站亿卤,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏鹿霸。R本人自食惡果不足惜排吴,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,254評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望杜跷。 院中可真熱鬧傍念,春花似錦、人聲如沸葛闷。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)淑趾。三九已至阳仔,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間扣泊,已是汗流浹背近范。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留延蟹,地道東北人评矩。 一個(gè)月前我還...
    沈念sama閱讀 45,497評(píng)論 2 354
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像阱飘,于是被迫代替她去往敵國(guó)和親斥杜。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,786評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容

  • iOS開發(fā)中, 之前一直使用swift, 因此對(duì)于Objective-C的內(nèi)存管理機(jī)制長(zhǎng)期處于混亂的一知半解狀態(tài)....
    icetime17閱讀 838評(píng)論 1 8
  • 29.理解引用計(jì)數(shù) Objective-C語(yǔ)言使用引用計(jì)數(shù)來管理內(nèi)存沥匈,也就是說蔗喂,每個(gè)對(duì)象都有個(gè)可以遞增或遞減的計(jì)數(shù)...
    Code_Ninja閱讀 1,470評(píng)論 1 3
  • 1.1 什么是自動(dòng)引用計(jì)數(shù) 概念:在 LLVM 編譯器中設(shè)置 ARC(Automaitc Reference Co...
    __silhouette閱讀 5,081評(píng)論 1 17
  • 內(nèi)存管理 簡(jiǎn)述OC中內(nèi)存管理機(jī)制缰儿。與retain配對(duì)使用的方法是dealloc還是release,為什么散址?需要與a...
    丶逐漸閱讀 1,948評(píng)論 1 16
  • 秋日 推開門 濃烈的桂花香 大片溫暖的陽(yáng)光 頭頂上藍(lán)藍(lán)的天空 操場(chǎng)上打球的小伙兒 留下怔怔的我 披散著頭發(fā) 氣若游...
    星梓馨閱讀 217評(píng)論 0 1