前言
伴隨著iOS5的發(fā)布,在Xcode4.2中加入了一個(gè)振奮人心的新特性。ARC,開啟這個(gè)特性后,幫我們省去了許多內(nèi)存管理的代碼,讓我們把更多的精力集中到功能的實(shí)現(xiàn)上。雖然ARC如此的完美,但作為iOS Developer,學(xué)習(xí)MRC,同樣的重要。
為什么要學(xué)內(nèi)存管理 ?
- iOS應(yīng)用程序出現(xiàn)Crash(閃退),90%的原因是因?yàn)閮?nèi)存問題
- 在iPhone 6s以前,大多數(shù)iPhone的運(yùn)行內(nèi)存都是1G,每個(gè)應(yīng)用程序啟動(dòng)后分配的內(nèi)存空間極其有限,當(dāng)應(yīng)用程序運(yùn)行過程中所占用的內(nèi)存較大時(shí),便會收到系統(tǒng)給出的內(nèi)存警告,如果應(yīng)用程序所占用的內(nèi)存超過限制時(shí),便會被系統(tǒng)強(qiáng)制關(guān)閉,所以我們需要對應(yīng)用程序進(jìn)行內(nèi)存管理,一個(gè)好的程序程序也應(yīng)該盡可能少地占用內(nèi)存
內(nèi)存管理的兩種方式
- MRC
- Mannul Reference Counting
- 手動(dòng)引用計(jì)數(shù)
- ARC
- Automatic Reference Counting
- 自動(dòng)引用計(jì)數(shù)
- 從Xcode4.2過后,系統(tǒng)默認(rèn)就開啟了ARC,但我們可以手動(dòng)選擇使用MRC

哪些變量需要做內(nèi)存管理
- 通常由程序員自己創(chuàng)建的對象(繼承于NSObject)需要做內(nèi)存管理,因?yàn)樗麄兇鎯υ诙褍?nèi)存中
- 而基本數(shù)據(jù)類型,比如int, float,double,char,struct等不需要做內(nèi)存管理,因?yàn)樗鼈兇鎯υ跅?nèi)存中,棧內(nèi)存中的變量會自己管理自己。
MRC
- Mannul Reference Counting
- 手動(dòng)引用計(jì)數(shù)
- 在iOS5以前,程序員普遍使用MRC方式來管理內(nèi)存,程序員需要自己添加retain、release和autorelease等內(nèi)存管理代碼來跟蹤自己所擁有的對象以明確地管理對象的內(nèi)存,在需要使用該對象的時(shí)候保證該對象一直存在于內(nèi)存中,在不需要使用該對象的時(shí)候保證該對象所占用的內(nèi)存被系統(tǒng)正常回收
- 為了讓系統(tǒng)知道何時(shí)需要將某個(gè)對象所占用的內(nèi)存清理掉,系統(tǒng)引入了引用計(jì)數(shù)器的概念
引用計(jì)數(shù)器
- 概念
- 系統(tǒng)為每個(gè)OC對象內(nèi)部都分配了4個(gè)字節(jié)的存儲空間存放自己的引用計(jì)數(shù)器,引用計(jì)數(shù)器是一個(gè)整數(shù),表示“對象被引用的次數(shù)”,當(dāng)對象的引用計(jì)數(shù)器為0時(shí),對象所占用的內(nèi)存空間就會被系統(tǒng)自動(dòng)回收,當(dāng)對象的引用計(jì)數(shù)器不為0時(shí),在程序運(yùn)行過程中所占用的內(nèi)存會一直存在,直到整個(gè)程序退出時(shí)由OS自動(dòng)釋放
- 操作應(yīng)用計(jì)數(shù)器
- 當(dāng)使用alloc、new、copy、mutableCopy創(chuàng)建一個(gè)新對象時(shí),該新對象的引用計(jì)數(shù)器為1
- 當(dāng)給對象發(fā)送一條retain消息時(shí),對象的引用計(jì)數(shù)器+1(方法返回對象本身)
- 當(dāng)給對象發(fā)送一條release消息時(shí)對象的引用計(jì)數(shù)器-1(方法無返回值)
- 當(dāng)給對象發(fā)送一條retainCount消息時(shí),返回對象的當(dāng)前引用計(jì)數(shù)器(不要以該數(shù)據(jù)來判斷對象是否被釋放)
- 例子
Person *p = [[Person alloc] init]; // 使用alloc創(chuàng)建一個(gè)新對象,對象引用計(jì)數(shù)器 = 1
[p retain]; // 給對象發(fā)送一條retain消息,對象引用計(jì)數(shù)器 + 1 = 2
[p release]; // 給對象發(fā)送一條release消息,對象引用計(jì)數(shù)器 - 1 = 1
[p release]; // 給對象發(fā)送一條release消息,對象引用計(jì)數(shù)器 - 1 = 0,指針?biāo)赶虻膶ο蟮膬?nèi)存被釋放
- 注意
- 當(dāng)引用計(jì)數(shù)器為0時(shí),會自動(dòng)調(diào)用dealloc方法
dealloc方法
- 當(dāng)系統(tǒng)回收對象的內(nèi)存時(shí),系統(tǒng)會自動(dòng)給該對象發(fā)送一條dealloc消息,我們一般會重寫dealloc方法,在這里給當(dāng)前對象所擁有的資源(包括實(shí)例變量)發(fā)送一條release消息(基本數(shù)據(jù)類型不用),保證自身所擁有的資源也可以正常釋放(因?yàn)樵谑褂迷撡Y源的時(shí)候,采用retain獲取了該資源的所有權(quán),在自身釋放的同時(shí),也應(yīng)該放棄對該資源的所有權(quán))
- (void)dealloc
{
NSLog(@"Person dealloc");
// release對象所擁有資源
[_room release];
// 設(shè)置為nil可以避免野指針錯(cuò)誤(其實(shí)可以不設(shè)置,只是寫了顯得有逼格)
_room = nil;
[super dealloc];
}
- 注1: 不要直接調(diào)用對象的dealloc方法
- 注2: 重寫dealloc方法時(shí),一定要調(diào)用[super dealloc]方法,且放在代碼的最后
- 注3: 當(dāng)應(yīng)用程序被關(guān)掉,dealloc方法不一定會被調(diào)用,因?yàn)橛上到y(tǒng)OS直接來釋放內(nèi)存比調(diào)用dealloc釋放內(nèi)存效率得多
- 注4: 不要在dealloc方法中管理稀缺資源(比如網(wǎng)絡(luò)連接,文件資源,DOS命令等),因?yàn)閐ealloc并不一定都是立即調(diào)用,有可能會延遲調(diào)用,也可能根本不會被調(diào)用
僵尸對象、野指針與空指針
- 僵尸對象
- 所占用的內(nèi)存已經(jīng)被回收的對象,僵尸對象不能再使用
- 野指針
- 指向僵尸對象的指針,給野指針發(fā)送消息會報(bào)錯(cuò)EXC_BAD_ACCESS錯(cuò)誤:訪問了一塊已經(jīng)被回收的內(nèi)存
- 空指針
- 沒有指向任何對象的指針(存儲的東西是nil,NULL,0),給空指針發(fā)送消息不會報(bào)錯(cuò),系統(tǒng)什么也不會做,所以在對象被釋放時(shí)將指針設(shè)置為nil可以避免野指針錯(cuò)誤
注: 默認(rèn)情況下,Xcode是不會監(jiān)聽僵尸對象的,所以需要我們自己手動(dòng)開啟,開啟監(jiān)聽僵尸對象步驟為: Edit Scheme ->; Run ->; Diagnostics ->; Objective-C的Enable Zombie Objects打鉤,這樣便可以在因?yàn)榻┦瑢ο髨?bào)錯(cuò)的時(shí)候給出更多錯(cuò)誤信息
- 例子
// 引用計(jì)數(shù)器 = 1
Person *p = [[Person alloc] init];
// 引用計(jì)數(shù)器 - 1 = 0,指針?biāo)赶虻膶ο蟮膬?nèi)存被釋放
[p release];
// 這句給野指針發(fā)送消息,會報(bào)野指針錯(cuò)誤,開啟監(jiān)聽僵尸對象會給出錯(cuò)誤信息
// -[Person release]: message sent to deallocated instance 0x100206fd0
[p release];
- 如何避免野指針問題
- 如果在第一次給對象發(fā)送release消息后,立刻將指針置空,便不會出現(xiàn)野指針錯(cuò)誤,因?yàn)榻o空指針發(fā)送消息不會報(bào)錯(cuò),系統(tǒng)什么也不會做,所以在對象被釋放時(shí)將指針設(shè)置為nil可以避免野指針錯(cuò)誤
自動(dòng)釋放池
自動(dòng)釋放池提供了延遲放棄一個(gè)對象的所有權(quán)的機(jī)制,比如想要在一個(gè)方法中返回一個(gè)對象,如果先使用release放棄了該對象的所有權(quán),那么return返回的對象便是一個(gè)僵尸對象,如果先進(jìn)行return返回,那么便無法放棄該對象的所有權(quán),導(dǎo)致了內(nèi)存泄漏
- 創(chuàng)建
- iOS5.0之前創(chuàng)建自動(dòng)釋放池方法(現(xiàn)在也可使用)
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
// do something...
[pool release];
- iOS5.0之后創(chuàng)建自動(dòng)釋放池方法
@autoreleasepool
{
// do something...
}
- autorelease方法
- 是一種支持引用計(jì)數(shù)的內(nèi)存管理方式,只要在自動(dòng)釋放池中給對象發(fā)送一條autorelease消息,就會將對象放到自動(dòng)釋放池中,當(dāng)自動(dòng)釋放池被銷毀時(shí),會對池中的所有對象發(fā)送一條release消息
- autorelease方法會返回對象本身
- autorelease方法不會修改對象的引用計(jì)數(shù)器
- autorelease方法可以讓開發(fā)者不用實(shí)時(shí)關(guān)心什么時(shí)候發(fā)送release消息
注1: 自動(dòng)釋放池被銷毀時(shí),只是給池中所有對象發(fā)送一條release消息,不代表對象一定會被釋放
注2: 對象在自動(dòng)釋放池中每收到一條autorelease消息,在自動(dòng)釋放池被銷毀時(shí),對象都會收到一次release消息
- autorelease方法使用注意事項(xiàng)
- 一定要在自動(dòng)釋放池中調(diào)用autorelease方法,才會將對象放入自動(dòng)釋放池中
- 即使在自動(dòng)釋放池內(nèi)創(chuàng)建對象,只要不調(diào)用了autorelease方法,就不會將對象放入自動(dòng)釋放池中
- 即使在自動(dòng)釋放池外創(chuàng)建對象,只要在自動(dòng)釋放池中調(diào)用了autorelease方法,就會將對象放入自動(dòng)釋放池中
- 一個(gè)程序中可以創(chuàng)建N個(gè)自動(dòng)釋放池,且自動(dòng)釋放池可以嵌套,這些自動(dòng)釋放池以棧結(jié)構(gòu)存在(先進(jìn)后出),當(dāng)一個(gè)對象調(diào)用autorelease方法時(shí),會將這個(gè)對象放到棧頂?shù)淖詣?dòng)釋放池中
- autorelease不能精準(zhǔn)地釋放內(nèi)存(延遲釋放),因?yàn)橐獙⒊刂械乃袃?nèi)容都執(zhí)行完才會釋放自動(dòng)釋放池,所以占用內(nèi)存比較大的東西還是使用release為宜
ARC
Automatic Reference Counting
自動(dòng)引用計(jì)數(shù)
它是iOS4引入的一項(xiàng)新技術(shù)(從iOS5開始支持弱引用),其使用與MRC相同的內(nèi)存管理規(guī)則來管理內(nèi)存,不過編譯器會在編譯階段自動(dòng)地在適當(dāng)?shù)奈恢貌迦雛etain、release和autorelease等內(nèi)存管理代碼來管理內(nèi)存(屬于編譯器特性,不是運(yùn)行時(shí)特性),不再需要程序人員手動(dòng)管理.官方強(qiáng)烈建議使用ARC方式來管理內(nèi)存
注: OC中的ARC和Java中的垃圾回收機(jī)制不一樣,Java中的垃圾回收是系統(tǒng)做的,而OC中的ARC是編譯器做的
- MRC和ARC示例
// MRC
@interface Person : NSObject
@property (retain) NSNumber *number;
@end
@implementation Person
- (void)dealloc
{
NSLog(@"Person dealloc");
[_number release];
[super dealloc];
}
@end
Person *person = [[Person alloc] init];
NSNumber *number = [[NSNumber alloc] initWithInt:2];
person.number = number;
[number release];
[person release];
// perosn和number正常被釋放
// ARC
@interface Person : NSObject
@property (strong) NSNumber *number;
@end
@implementation Person
- (void)dealloc
{
NSLog(@"Person dealloc");
}
@end
Person *person = [[Person alloc] init];
NSNumber *number = [[NSNumber alloc] initWithInt:2];
person.number = number;
// perosn和number出了作用域正常被釋放
-
ARC與MRC的混合開發(fā)
- 如果想在ARC項(xiàng)目中使用MRC文件,可以在Build Phases中的Compile Sources中對應(yīng)文件加入編譯標(biāo)記-fno-objc-arc
- 如果想在MRC項(xiàng)目中使用ARC文件,可以在Build Phases中的Compile Sources中對應(yīng)文件加入編譯標(biāo)記-fobjc-arc
-
ARC引入的新規(guī)則
- 為了使ARC能夠正常工作,在ARC中引入了一些區(qū)別于當(dāng)前編譯模式的新的規(guī)則,如果你違反了這些規(guī)則,在編譯階段編譯器會給出一個(gè)警告
- 不能實(shí)現(xiàn)或者調(diào)用retain、release、autorelease和retainCount方法,甚至不能使用@selector(retain)、@selector(release)等方式調(diào)用
- 不能調(diào)用dealloc方法
- 可以實(shí)現(xiàn)dealloc方法,用于釋放除了實(shí)例變量以外的其他資源
- 不需要在這里釋放實(shí)例變量(實(shí)際上也不能在這里給實(shí)例變量發(fā)送release消息)
- 可以在這里調(diào)用[systemClassInstance setDelegate:nil],以便處理不是用ARC編譯的systemClass(在MRC下delegate使用assign修飾,如果自身被釋放,delegate會變成野指針,所以需要在dealloc中將其置空;在ARC下delegate使用weak修飾,如果自身被釋放,delegate會自動(dòng)置空)
- 不需要調(diào)用[super dealloc],編譯器會自動(dòng)調(diào)用
不能使用NSAutoreleasePool來創(chuàng)建自動(dòng)釋放池,而是需要使用@autoreleasepool來代替
為了與MRC之間進(jìn)行互相操作,ARC中不允許給存取器命名為以new開頭(即不能聲明以new開頭的屬性),除非為該屬性定義一個(gè)新的getter名稱為了與MRC之間進(jìn)行互相操作,ARC中不允許給存取器命名為以new開頭(即不能聲明以new開頭的屬性),除非為該屬性定義一個(gè)新的getter名稱
// 錯(cuò)誤
@property NSString *newTitle;
// 正確
@property (getter=theNewTitle) NSString *newTitle;
ARC引入的新特性
兩個(gè)屬性修飾符: strong和weak
- 在ARC中新增了兩個(gè)屬性修飾符: strong和weak,其中strong是默認(rèn)修飾符,下面介紹一下這兩個(gè)屬性修飾符與retain和assign的區(qū)別
// 下面這句對于strong的示例,與此同義: @property(retain) MyClass *myObject;
@property(strong) MyClass *myObject;
// 下面這句對于weak的示例,與此相似: @property(assign) MyClass *myObject;
// 使用assign修飾的指針?biāo)赶虻膶ο笕绻会尫?該指針會變成野指針;使用weak修飾的指針?biāo)赶虻膶ο笕绻会尫?該指針會變成空指針
@property(weak) MyClass *myObject;
- 針對于ARC中屬性修飾符的使用,要進(jìn)行如下變化
- strong用于OC對象,相當(dāng)于MRC中的retain
- weak用于OC對象,相當(dāng)于MRC中的assign
- assign用于基本數(shù)據(jù)類型,相當(dāng)于MRC中的assign
注: 其實(shí)就是將MRC中的assign分成了兩個(gè)部分,分別用于修飾OC對象與基本數(shù)據(jù)類型
四個(gè)變量修飾符
在ARC中新增了四個(gè)變量修飾符: 雙下劃線strong、雙下劃線weak、雙下劃線unsafe_unretained和雙下劃線autoreleasing,其中雙下劃線strong是默認(rèn)修飾符,下面介紹一下這四個(gè)變量修飾符
- 雙下劃線strong: 強(qiáng)引用,只要有強(qiáng)指針指向該變量,該變量便會一直存在
- 雙下劃線weak: 弱引用,只要沒有強(qiáng)指針指向該變量,該變量便會被置空(即設(shè)置為nil)
- 雙下劃線unsafe_unretained: 不安全的弱引用,只要沒有強(qiáng)指針指向該變量,該變量不會被置空(即設(shè)置為nil),而會變成野指針
- 雙下劃線autoreleasing: 用于標(biāo)示自動(dòng)釋放的變量
- 官方提醒,在為變量添加修飾符時(shí),最正確的方式如下
// 規(guī)則
ClassName * qualifier variableName;
// 正確示例
MyClass * __weak myWeakReference;
MyClass * __unsafe_unretained myUnsafeReference;
// 錯(cuò)誤示例(雖然錯(cuò)誤,但是編譯器會默認(rèn)為正確,官方說法為"forgiven")
__weak MyClass * myWeakReference;
__unsafe_unretained MyClass * myUnsafeReference;
注: 在直接使用__weak修飾變量指向一個(gè)剛創(chuàng)建的對象時(shí),需要注意對象剛剛創(chuàng)建出來就會釋放的情況
NSString * __weak string = [[NSString alloc] initWithFormat:@"loly"];
// 因?yàn)闆]有強(qiáng)指針指向該對象,該對象會立即被釋放
最后
溫故而知新。