主要內(nèi)容:
- 內(nèi)存區(qū)域劃分
- 內(nèi)存管理/引用計(jì)數(shù)
-
MRC
手動(dòng)管理引用計(jì)數(shù) -
ARC
自動(dòng)引用計(jì)數(shù) - 內(nèi)存泄漏問題
- 野指針問題
一、內(nèi)存區(qū)域劃分
程序在分配內(nèi)存時(shí),主要分為:棧區(qū)、堆區(qū)、靜態(tài)區(qū)、常量區(qū)、代碼區(qū);
內(nèi)存區(qū)域 | 具體說明 |
---|---|
棧區(qū) | 存放局部變量的值,系統(tǒng)自動(dòng)分配和釋放; 特點(diǎn):容量小,速度快,有序 |
堆區(qū) | 存放通過malloc 系列函數(shù)或new 操作符分配的內(nèi)存,如對(duì)象;一般由程序員分配和釋放,如果不釋放,則出現(xiàn)內(nèi)存泄露; 特點(diǎn):容量大,速度慢,無(wú)序; |
靜態(tài)區(qū) | 存放全局變量和靜態(tài)變量(包括靜態(tài)局部變量和靜態(tài)全局變量); 當(dāng)程序結(jié)束時(shí),系統(tǒng)回收; |
常量區(qū) | 存放常量的內(nèi)存區(qū)域; 程序結(jié)束時(shí),系統(tǒng)回收; |
代碼區(qū) | 存放二進(jìn)制代碼的區(qū)域 |
從上述分類上看,我們?cè)陂_發(fā)過程中主要涉及的是堆上內(nèi)存的管理。通常,我們創(chuàng)建一個(gè)對(duì)象的代碼如下:
NSObject *obj = [[NSObject alloc] init];
上述代碼創(chuàng)建了一個(gè)NSObject
類型的指針obj
和一個(gè)NSObject
類型的對(duì)象。obj
指針存在棧上,而其指向的對(duì)象則是在堆上。這種對(duì)象也稱之為堆對(duì)象。
二、內(nèi)存管理/引用計(jì)數(shù)
無(wú)論是MRC
還是ARC
環(huán)境,Objective-C
都采用引用計(jì)數(shù)來管理內(nèi)存;每個(gè)對(duì)象都有一個(gè)引用計(jì)數(shù)器,任何時(shí)候指向?qū)ο蟮闹羔槀€(gè)數(shù)和對(duì)象的引用計(jì)數(shù)相等,當(dāng)一個(gè)對(duì)象的引用計(jì)數(shù)為0
的時(shí)候?qū)?huì)被釋放;
OC
管理內(nèi)存涉及到對(duì)象的"生成"、"持有"、"釋放",MRC
需要調(diào)用對(duì)應(yīng)的方法來管理引用計(jì)數(shù),而ARC
則是自動(dòng)管理引用計(jì)數(shù),無(wú)需再調(diào)用這些內(nèi)存管理的方法。雖然兩者管理內(nèi)存的形式不同,但是它們都遵循相同的內(nèi)存管理規(guī)律,內(nèi)容如下:
- 自己生成的對(duì)象,自己所持有;
- 非自己生成的對(duì)象,自己也能持有;
- 不再需要自己持有對(duì)象時(shí)釋放;
- 非自己持有的對(duì)象無(wú)法釋放;
三、MRC手動(dòng)管理引用計(jì)數(shù)
MRC
,即手動(dòng)管理引用計(jì)數(shù)。當(dāng)我們通過alloc
、retain
等方法持有對(duì)象后,也必須有相應(yīng)的release
或者autorelease
將其釋放。總結(jié)對(duì)象操作與Objective-C
內(nèi)存方法對(duì)應(yīng)關(guān)系如下:
對(duì)象操作 | OC方法 |
---|---|
生成并持有對(duì)象 | 以alloc /new /copy /mutableCopy 等名稱開頭方法 |
持有對(duì)象 |
retain 方法 |
釋放對(duì)象 |
release 方法 |
廢棄對(duì)象 |
dealloc 方法 |
1.自己生成的對(duì)象,自己所持有/非自己生成對(duì)象,不持有
id obj = [[NSObject alloc] init]; //自己生成并持有對(duì)象
id obj1 = [NSMutableArray array]; //取得非自己生成的對(duì)象,但不持有對(duì)象
OC
中使用alloc
、new
、copy
、mutableCopy
這些名稱開頭的方法意味著自己生成對(duì)象并持有,否則就是非自己生成的對(duì)象不持有。如上源碼,使用NSObject
類的alloc
類方法就能自己生成并持有對(duì)象,指向生成并持有對(duì)象的指針被賦值給了obj
。
通過自定義方法來理解這兩種創(chuàng)建對(duì)象方法的區(qū)別(系統(tǒng)方法也是類似的實(shí)現(xiàn)),測(cè)試代碼如下:
//以alloc開頭的方法
- (id)allocObject {
id obj = [[NSObject alloc] init];
return obj;
}
- (id)object {
id obj = [[NSObject alloc] init];
[obj autorelease]; //用該方法,可以使取得的對(duì)象存在,但是自己不持有對(duì)象
return obj;
}
autorelease
即自動(dòng)釋放,對(duì)象已經(jīng)加入自動(dòng)釋放池,所以獲取對(duì)象并不持有;涉及到的自動(dòng)釋放池的內(nèi)容會(huì)在后續(xù)詳細(xì)總結(jié)。
注意:生成并持有對(duì)象的的方法一定是駝峰拼寫來命名的方法,如alloc
、allocMyObject
等方法;相反allocate
、mutableCopyed
就不屬于這類方法;
2.非自己生成的對(duì)象,自己也能持有
id obj1 = [NSMutableArray array]; //取得非自己生成的對(duì)象,但不持有對(duì)象
[obj retain]; //通過retain方法,持有了對(duì)象
源代碼中,NSMutableArray
類對(duì)象被賦值給變量obj
,但是變量obj
自己不持有該對(duì)象。使用retain
方法后可以持有對(duì)象。
3.不再需要自己持有對(duì)象時(shí)釋放
自己持有的對(duì)象,一旦不需要,持有者有義務(wù)釋放該對(duì)象,釋放對(duì)象使用release方法。
id obj = [[NSObject alloc] init]; //自己生成并持有對(duì)象
[obj release]; //釋放自己持有的對(duì)象
NSLog(@"%@",obj); //已經(jīng)釋放,再次使用會(huì)崩潰
雖然指向?qū)ο蟮闹羔樢廊槐A粼谧兞?code>obj中,看似可以訪問,但對(duì)象一經(jīng)釋放就絕不可再訪問。
4.非自己持有的對(duì)象無(wú)法釋放
在應(yīng)用程序中釋放非自己持有的對(duì)象就會(huì)造成崩潰,使用代碼演示如下:
//情況1:釋放完不再需要的對(duì)象后再次釋放,訪問了已經(jīng)廢棄的對(duì)象而崩潰!
id obj = [[NSObject alloc] init];
[obj release];
[obj release];
//情況2:取得自己并不持有的對(duì)象對(duì)其釋放,釋放了非自己持有的對(duì)象而崩潰!
id obj = [[NSMutableArray array];
[obj release];
四、ARC自動(dòng)引用計(jì)數(shù)
ARC(Automic Reference Counting)
,即自動(dòng)引用計(jì)數(shù);這是iOS5
推出的新特性,iOS4.3
也支持ARC
,只是不能使用weak
。ARC
不再需要使用類似retain
、release
的操作來持有或者釋放對(duì)象,從而大大提高了開發(fā)效率;
1.ARC使用條件
-
Xcode4.2
或以上版本 - 使用
LLVM
編輯器3.0
或以上版本 -
Xcode
編譯器選項(xiàng)中設(shè)置ARC
有效
2.ARC基本原理
-
ARC下
的編譯器會(huì)在代碼編譯階段合適的位置,自動(dòng)加入retain/release/autorelease
的操作; -
ARC
的規(guī)則:只要還有一個(gè)強(qiáng)引用指針指向?qū)ο螅瑢?duì)象就會(huì)保存在內(nèi)存中; -
ARC
中使用strong
和weak
關(guān)鍵字來修飾對(duì)象;strong
表示強(qiáng)引用,對(duì)應(yīng)MRC
下的retain
;weak
表示弱引用,對(duì)應(yīng)原來的assign
,不同的是當(dāng)對(duì)象被釋放的時(shí)候,對(duì)象weak
指針自動(dòng)賦值為nil
,從而不會(huì)引發(fā)野指針錯(cuò)誤;
3.ARC所有權(quán)修飾符
ARC
有效時(shí),OC
處理id
類型和對(duì)象類型必須附加所有權(quán)修飾符。所有權(quán)修飾符一共有四種:
__strong
__weak
__unsafe_unretained
__autoreleasing
__strong修飾符:
__strong
是id
類型和對(duì)象類型默認(rèn)的所有權(quán)修飾,表示對(duì)對(duì)象的"強(qiáng)引用";當(dāng)對(duì)象沒有任何一個(gè)強(qiáng)引用指向它的時(shí)候,對(duì)象將被釋放。
__weak修飾符:
1.__weak
與__strong
修飾符的作用相反,表示弱引用,不會(huì)增加引用計(jì)數(shù);
2.當(dāng)對(duì)象被釋放后,所有指向它的弱引用都會(huì)被置為nil
,這樣避免了野指針問題。
3.__weak
修飾符常用于解決循環(huán)引用問題;
4.__weak
只能用于iOS5
以上版本,更早的版本只能使用__unsafe_unretained
修飾符。
__unsafe_unretained修飾符:
1.__unsafe_unretained
提供弱引用,與__weak
作用類似;
2.__unsafe_unretained
不能在對(duì)象釋放后自動(dòng)置為nil
,易產(chǎn)生野指針問題;
3.__unsafe_unretained
可用于iOS5
之前版本,為兼容ARC
弱引用而引入;
__autoreleasing修飾符:
將對(duì)象賦值給附有__autoreleasing
修飾符的變量,
等同于在MRC
下調(diào)用對(duì)象的autorelease
方法,即對(duì)象被注冊(cè)到autoreleasepool
ARC
環(huán)境不能使用NSAutoreleasePool
類也不能調(diào)用autorelease
方法,代替它們實(shí)現(xiàn)對(duì)象自動(dòng)釋放的是@autoreleasepool塊
和__autoreleasing
修飾符;兩種環(huán)境下的使用情況類比如下圖:
如圖所示,@autoreleasepool
塊替換了NSAutoreleasePoool
類對(duì)象的生成、持有及廢棄這一過程。而附有__autoreleasing
修飾符的變量替代了autorelease
方法,將對(duì)象注冊(cè)到了autoreleasepool
;
但事實(shí)上,顯式使用__autoreleasing
修飾符的情況非常少見,這主要是因?yàn)?code>ARC的很多情況下,即使是不顯式的使用__autoreleasing
,也能實(shí)現(xiàn)對(duì)象被注冊(cè)到釋放池中。這其中就包括以下的幾種情況:
- 編譯器檢查方法名是否以
alloc/new/copy/mutableCopy
開始,如果不是則自動(dòng)將返回對(duì)象注冊(cè)到autoreleasepool
; - 訪問附有
__weak
修飾符的變量時(shí),實(shí)際上必定要訪問注冊(cè)到autoreleasepool
的對(duì)象; - id的指針或?qū)ο蟮闹羔樤跊]有顯式地指定修飾符時(shí)候,會(huì)被默認(rèn)附加上
__autoreleasing
修飾符;
4.ARC屬性修飾符
ARC
中的所有權(quán)修飾與屬性修飾符存在著對(duì)應(yīng)關(guān)系,如果不一致還會(huì)引起編譯錯(cuò)誤。總結(jié)兩者的對(duì)應(yīng)關(guān)系如下:
屬性修飾符 | 所有權(quán)修飾符 |
---|---|
assign |
__unsafe_unretained |
copy |
__strong (但是賦值的是被復(fù)制的對(duì)象) |
retain |
__strong |
strong |
__strong |
unsafe_unretained |
__unsafe_unretained |
weak |
__weak |
以上各種屬性只有copy
不是簡(jiǎn)單的賦值,它賦值的是通過NSCopying
接口的copyWithZone:
方法復(fù)制賦值源生成的對(duì)象。
5.ARC管理內(nèi)存的規(guī)則
- 不能使用
retain/release/retainCount/autorelease
內(nèi)存管理方法; - 不能使用
NSAllocateObject/NSDeallocateObject
方法; - 必須遵守內(nèi)存管理的方法命名規(guī)則;
- 不能顯式調(diào)用
dealloc
方法,如[super dealloc]
; - 使用
@autoreleasepool
塊代替NSAutoreleasePool
; - 不能使用區(qū)域(
NSZone
); - 對(duì)象類型變量不能作為C語(yǔ)言結(jié)構(gòu)體(
struct/union
)的成員; - 顯式轉(zhuǎn)換
id
和void *
6.必須遵守內(nèi)存管理的方法命名規(guī)則
MRC
下,用于對(duì)象生成/持有的方法必須遵守alloc
、new
、copy
、mutableCopy
的命名規(guī)則。以這些名稱開始的方法在返回對(duì)象時(shí),必須返回給調(diào)用方所應(yīng)當(dāng)持有的對(duì)象。這在ARC
環(huán)境下的規(guī)則一樣。只是ARC
下關(guān)于init
開發(fā)的方法規(guī)則要更加嚴(yán)格了:
- 必須是實(shí)例方法,且返回對(duì)象;
- 返回對(duì)象應(yīng)該是
id
類型或該方法聲明類的對(duì)象,抑或該類的超類或子類; - 該返回類型不注冊(cè)到
autoreleasepool
上; - 基本上,
init
方法只是對(duì)alloc
方法返回值的對(duì)象進(jìn)行初始化處理并返回對(duì)象;
7.顯式轉(zhuǎn)換id和void *
這里說到的其實(shí)就是Core Foundation
和Foundation
兩者之間的轉(zhuǎn)換。
Core Foundation
是由C
語(yǔ)言實(shí)現(xiàn)的,而Foundation
由Objective-C
實(shí)現(xiàn),兩者可以相互轉(zhuǎn)換。
MRC
不存在顯式轉(zhuǎn)換的問題,因?yàn)楸緛砭褪鞘謩?dòng)管理內(nèi)存。但是為了在ARC
也能實(shí)現(xiàn)對(duì)Core Foundation
對(duì)象的自動(dòng)內(nèi)存管理,我們就必須將其與Objective-C
對(duì)象的轉(zhuǎn)換。Objective-C
中提供了三個(gè)關(guān)鍵字__bridge
、__bridge_retained
、__bridge_transfer
來實(shí)現(xiàn)轉(zhuǎn)換。
情況1:__bridge轉(zhuǎn)換
/*
MRC代碼下,將id變量直接強(qiáng)制轉(zhuǎn)換void*正常,但ARC下報(bào)錯(cuò)
id obj = [[NSObject alloc] init];
void *p = obj;
*/
//ARC下代碼使用__bridge實(shí)現(xiàn)單純的賦值轉(zhuǎn)換
id obj = [[NSObject alloc] init];
void *p = (__bridge void *)(obj);
__bridge
可實(shí)現(xiàn)Objective-C
對(duì)象和Core Foundation
對(duì)象的相互轉(zhuǎn)換;但是其安全性與賦值給__unsafe_unretained
修飾符相近,甚至?xí)汀H绻芾頃r(shí)不注意賦值對(duì)象的所有者,就容易產(chǎn)生野指針錯(cuò)誤導(dǎo)致程序崩潰。
情況2:__bridge_retained轉(zhuǎn)換
id obj = [[NSObject alloc] init];
void *p = (__bridge_retained void *)(obj);
/*相當(dāng)于MRC代碼:
id obj = [[NSObject alloc] init];
void *p = obj;
[(id)p retain];
*/
__bridge_retained
轉(zhuǎn)換可使要轉(zhuǎn)換的變量也持有所賦值的對(duì)象。此操作類似于retain
。上述代碼中變量obj
和變量p
同時(shí)持有對(duì)象。
情況3:__bridge_transfer轉(zhuǎn)換
id obj = (__bridge_transfer id)p;
/*相當(dāng)于MRC代碼:
id obj = id(p)
[obj retain];
[(id)p release];
*/
__bridge_transfer
轉(zhuǎn)換提供與__bridge_retained
相反的動(dòng)作,被轉(zhuǎn)換的變量所持有的對(duì)象在該變量被賦值給轉(zhuǎn)換的目標(biāo)后隨之釋放。此操作與release
相似。
五、內(nèi)存泄漏問題
內(nèi)存泄露就是本該廢棄的對(duì)象在超出其生命周期后繼續(xù)存在。導(dǎo)致系統(tǒng)內(nèi)存浪費(fèi)、程序運(yùn)行速度減慢甚至系統(tǒng)崩潰等嚴(yán)重后果;
總結(jié)常見的內(nèi)存泄露的異常情況如下:
-
AFNetworking
循環(huán)引用(未使用單例或者沒有調(diào)用銷毀NSURLSession
的方法; -
Block
循環(huán)引用 -
delegate
循環(huán)引用 -
NSTimer
循環(huán)引用 - 創(chuàng)建的非
OC
對(duì)象內(nèi)存,在使用完畢后未手動(dòng)釋放; - 循環(huán)操作創(chuàng)建大量臨時(shí)對(duì)象,導(dǎo)致內(nèi)存導(dǎo)致內(nèi)存暴漲;
- 地圖類處理,使用完畢后未及時(shí)銷毀地圖相關(guān)組件對(duì)象
六、野指針問題
野指針指針就是指向一個(gè)已經(jīng)刪除對(duì)象或者訪問受限內(nèi)存區(qū)域的指針;
注意:野指針不是nil
指針,而是指向”垃圾”內(nèi)存(不可用內(nèi)存)的指針;
總結(jié)ARC
下常見的野指針異常情況如下:
參考鏈接:
1.Foundation對(duì)象與Core Foundation對(duì)象間的轉(zhuǎn)換
2.iOS幾種容易忽略的內(nèi)存泄漏方式
3.ARC下野指針常見寫法