iOS/OS X内存管理(一):基本概念与原理
iOS/OS X內(nèi)存管理(一):基本概念與原理
發(fā)表于21小時(shí)前|?1585次閱讀| 來源CSDN|?8?條評(píng)論| 作者劉耀柱
移動(dòng)開發(fā)iOSObjective-C內(nèi)存管理內(nèi)存泄露局部變量開發(fā)經(jīng)驗(yàn) allowtransparency="true" frameborder="0" scrolling="no" src="http://hits.sinajs.cn/A1/weiboshare.html?url=http%3A%2F%2Fwww.csdn.net%2Farticle%2F2015-11-12%2F2826198%3Fref%3Dmyread&type=3&count=&appkey=&title=%E5%86%85%E5%AD%98%E7%AE%A1%E7%90%86%E6%98%AF%E5%9C%A8%E7%A8%8B%E5%BA%8F%E9%9C%80%E8%A6%81%E6%97%B6%E7%A8%8B%E5%BA%8F%E5%91%98%E5%88%86%E9%85%8D%E4%B8%80%E6%AE%B5%E5%86%85%E5%AD%98%E7%A9%BA%E9%97%B4%EF%BC%8C%E8%80%8C%E5%BD%93%E4%BD%BF%E7%94%A8%E5%AE%8C%E4%B9%8B%E5%90%8E%E5%B0%86%E5%AE%83%E9%87%8A%E6%94%BE%E3%80%82%E5%A6%82%E6%9E%9C%E7%A8%8B%E5%BA%8F%E5%91%98%E5%AF%B9%E5%86%85%E5%AD%98%E8%B5%84%E6%BA%90%E4%BD%BF%E7%94%A8%E4%B8%8D%E5%BD%93%EF%BC%8C%E6%9C%89%E6%97%B6%E4%B8%8D%E4%BB%85%E4%BC%9A%E9%80%A0%E6%88%90%E5%86%85%E5%AD%98%E8%B5%84%E6%BA%90%E6%B5%AA%E8%B4%B9%EF%BC%8C%E7%94%9A%E8%87%B3%E4%BC%9A%E5%AF%BC%E8%87%B4%E7%A8%8B%E5%BA%8Fcrach%E3%80%82%E6%9C%AC%E6%96%87%E4%BD%9C%E8%80%85%E4%BB%8E%E5%9F%BA%E6%9C%AC%E6%A6%82%E5%BF%B5%E5%BC%80%E5%A7%8B%EF%BC%8C%E5%89%96%E6%9E%90%E5%86%85%E5%AD%98%E7%AE%A1%E7%90%86%E7%9A%84%E6%96%B9%E6%B3%95%E4%B8%8E%E9%97%AE%E9%A2%98%E3%80%82&pic=&ralateUid=&language=zh_cn&rnd=1447398728228" width="22" height="16">摘要:內(nèi)存管理是在程序需要時(shí)程序員分配一段內(nèi)存空間,而當(dāng)使用完之后將它釋放。如果程序員對(duì)內(nèi)存資源使用不當(dāng),有時(shí)不僅會(huì)造成內(nèi)存資源浪費(fèi),甚至?xí)?dǎo)致程序crach。本文作者從基本概念開始,剖析內(nèi)存管理的方法與問題。CSDN移動(dòng)將持續(xù)為您優(yōu)選移動(dòng)開發(fā)的精華內(nèi)容,共同探討移動(dòng)開發(fā)的技術(shù)熱點(diǎn)話題,涵蓋移動(dòng)應(yīng)用、開發(fā)工具、移動(dòng)游戲及引擎、智能硬件、物聯(lián)網(wǎng)等方方面面。如果您想投稿、尋求《近匠》報(bào)道,或給文章挑錯(cuò),歡迎發(fā)送郵件至tangxy#csdn.net(請(qǐng)把#改成@)。?
在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)存問題。?
?
memory management from apple document?
基本概念
引用計(jì)數(shù)(Reference Count)
為了解釋引用計(jì)數(shù),我們做一個(gè)類比:員工在辦公室使用燈的情景。
?
引用Pro Multithreading and Memory Management for iOS and OS X的圖
- 當(dāng)?shù)谝粋€(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)燈離開辦公室。
內(nèi)存管理規(guī)則
從上面員工在辦公室使用燈的例子,我們對(duì)比一下燈的動(dòng)作與Objective-C對(duì)象的動(dòng)作有什么相似之處:
?
因?yàn)槲覀兪峭ㄟ^引用計(jì)數(shù)來管理燈,那么我們也可以通過引用計(jì)數(shù)來管理使用Objective-C對(duì)象。
?
引用Pro Multithreading and Memory Management for iOS and OS X的圖
而Objective-C對(duì)象的動(dòng)作對(duì)應(yīng)有哪些方法以及這些方法對(duì)引用計(jì)數(shù)有什么影響?
?
當(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一次來釋放對(duì)象objc,所以你需要release對(duì)象objc兩次,此時(shí)RC=0;而當(dāng)RC=0時(shí),系統(tǒng)會(huì)自動(dòng)調(diào)用dealloc方法釋放對(duì)象。
Autorelease Pool
在開發(fā)中,我們常常都會(huì)使用到局部變量,局部變量一個(gè)特點(diǎn)就是當(dāng)它超過作用域時(shí),就會(huì)自動(dòng)釋放。而autorelease pool跟局部變量類似,當(dāng)執(zhí)行代碼超過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ì)象。
?
引用Pro Multithreading and Memory Management for iOS and OS X的圖
iOS 5/OS X Lion前的(等下會(huì)介紹引入ARC的寫法)實(shí)例代碼如下:
[cpp]?view plaincopy
由于放在autorelease pool的對(duì)象并不會(huì)馬上釋放,如果有大量圖片數(shù)據(jù)放在這里的話,將會(huì)導(dǎo)致內(nèi)存不足。?
[cpp]?view plaincopy
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方法來管理Objective-C對(duì)象的內(nèi)存,而是引入一種新的內(nèi)存管理機(jī)制Automatic Reference Counting(ARC),簡(jiǎn)單來說,它讓編譯器來代替程序員來自動(dòng)加入retain和release方法來持有和放棄對(duì)象的所有權(quán)。
在ARC內(nèi)存管理機(jī)制中,id和其他對(duì)象類型變量必須是以下四個(gè)ownership qualifiers其中一個(gè)來修飾:
- __strong(默認(rèn),如果不指定其他,編譯器就默認(rèn)加入)
- __weak
- __unsafe_unretained
- __autoreleasing
所以在管理Objective-C對(duì)象內(nèi)存的時(shí)候,你必須選擇其中一個(gè),下面會(huì)用一些列子來逐個(gè)解釋它們的含義以及如何選擇它們。
__strong ownership qualifier
如果我想創(chuàng)建一個(gè)字符串,使用完之后將它釋放調(diào)用,使用MRC管理內(nèi)存的寫法應(yīng)該是這樣:
[cpp]?view plaincopy
而如果是使用ARC方式的話,就text對(duì)象無需調(diào)用release方法,而是當(dāng)text變量超過作用域時(shí),編譯器來自動(dòng)加入[text release]方法來釋放內(nèi)存。?
[cpp]?view plaincopy
而當(dāng)你將text賦值給其他變量anotherText時(shí),MRC需要retain一下來持有所有權(quán),當(dāng)text和anotherText使用完之后,各個(gè)調(diào)用release方法來釋放。?
[cpp]?view plaincopy
而使用ARC的話,并不需要調(diào)用retain和release方法來持有跟釋放對(duì)象。?
[cpp]?view plaincopy
除了當(dāng)__strong變量超過作用域時(shí),編譯器會(huì)自動(dòng)加入release語句來釋放內(nèi)存,如果你將__strong變量重新賦給它其他值,那么編譯器也會(huì)自動(dòng)加入release語句來釋放變量指向之前的對(duì)象。例如:?
[cpp]?view plaincopy
前面已經(jīng)提過內(nèi)存管理的四條規(guī)則:?
?
我們總結(jié)一下編譯器是按以下方法來實(shí)現(xiàn)的:
- 對(duì)于規(guī)則1和規(guī)則2,是通過__strong變量來實(shí)現(xiàn);
- 對(duì)于規(guī)則3來說,當(dāng)變量超過它的作用域或被賦值或成員變量被丟棄時(shí)就能實(shí)現(xiàn);
- 對(duì)于規(guī)則4,當(dāng)RC=0時(shí),系統(tǒng)就會(huì)自動(dòng)調(diào)用。
__weak ownership qualifier
其實(shí)編譯器根據(jù)__strong修飾符來管理對(duì)象內(nèi)存。但是__strong并不能解決引用循環(huán)(Reference Cycle)問題:對(duì)象A持有對(duì)象B,反過來,對(duì)象B持有對(duì)象A;這樣會(huì)導(dǎo)致不能釋放內(nèi)存造成內(nèi)存泄露問題。
引用Pro Multithreading and Memory Management for iOS and OS X的圖
舉一個(gè)簡(jiǎn)單的例子,有一個(gè)類Test有個(gè)屬性objc,有兩個(gè)對(duì)象test1和test2的屬性objc互相引用test1和test2:
[cpp]?view plaincopy
[cpp]?view plaincopy
如何解決?于是我們引用一個(gè)__weakownership qualifier,被它修飾的變量都不持有對(duì)象的所有權(quán),而且當(dāng)變量指向的對(duì)象的RC為0時(shí),變量設(shè)置為nil。例如:?
[cpp]?view plaincopy
由于text變量被__weak修飾,text并不持有@"Sam Lau"對(duì)象的所有權(quán),@"Sam Lau"對(duì)象一創(chuàng)建就馬上被釋放,并且編譯器給出警告??,所以打印結(jié)果為(null)。
所以,針對(duì)剛才的引用循環(huán)問題,只需要將Test類的屬性objc設(shè)置weak修飾符,那么就能解決。
[cpp]?view plaincopy
[cpp]?view plaincopy
__unsafe_unretained ownership qualifier
__unsafe_unretained ownership qualifier,正如名字所示,它是不安全的。它跟__weak相似,被它修飾的變量都不持有對(duì)象的所有權(quán),但當(dāng)變量指向的對(duì)象的RC為0時(shí),變量并不設(shè)置為nil,而是繼續(xù)保存對(duì)象的地址;這樣的話,對(duì)象有可能已經(jīng)釋放,但繼續(xù)訪問,就會(huì)造成非法訪問(Invalid Access)。例子如下:
[cpp]?view plaincopy
打印結(jié)果是內(nèi)存地址相同:?
?
如果將__unsafe_unretained改為weak的話,兩個(gè)打印結(jié)果將不同。?
[cpp]?view plaincopy
?
__autoreleasing ownership qualifier
引入ARC之后,讓我們看看autorelease pool有哪些變化。沒有ARC之前的寫法如下:
[cpp]?view plaincopy
引入ARC之后,寫法比之前更加簡(jiǎn)潔:?
[cpp]?view plaincopy
相比之前的創(chuàng)建、使用和釋放NSAutoreleasePool對(duì)象,現(xiàn)在你只需要將代碼放在@autoreleasepool塊即可。你也不需要調(diào)用autorelease方法了,只需要用__autoreleasing修飾變量即可。?
?
引用Pro Multithreading and Memory Management for iOS and OS X的圖?
但是我們很少或基本上不使用autorelease pool。當(dāng)我們使用XCode創(chuàng)建工程后,有一個(gè)app的入口文件main.m使用了它:?
[cpp]?view plaincopy
Property(屬性)
有了ARC之后,新的property modifier也被引入到Objective-C類的property,例如:
[cpp]?view plaincopy
總結(jié)
要想掌握iOS/OS X的內(nèi)存管理,首先要深入理解引用計(jì)數(shù)(Reference Count)這個(gè)概念以及內(nèi)存管理的規(guī)則;在沒引入ARC之前,我們都是通過retain和release方法來手動(dòng)管理內(nèi)存,但引入ARC之后,我們可以借助編譯器來幫忙自動(dòng)調(diào)用retain和release方法來簡(jiǎn)化內(nèi)存管理和減低出錯(cuò)的可能性。雖然__strong修飾符能夠執(zhí)行大多數(shù)內(nèi)存管理,但它不能解決引用循環(huán)(Reference Cycle)問題,于是又引入另一個(gè)修飾符__weak。被__strong修飾的變量都持有對(duì)象的所有權(quán),而被__weak修飾的變量并不持有對(duì)象所有權(quán)。下篇我們介紹使用工具如何解決常見內(nèi)存問題:野指針和內(nèi)存泄露。
參考資料
- Pro Multithreading and Memory Management for iOS and OS X
- Advanced Memory Management Programming Guide
作者簡(jiǎn)介:劉耀柱(@Sam_Lau_Dev),iOS Developer兼業(yè)余Designer,參與開發(fā)技術(shù)前線iOS項(xiàng)目翻譯,個(gè)人博客:http://www.jianshu.com/users/256fb15baf75/latest_articles,GitHub:https://github.com/samlaudev。?
第一時(shí)間掌握最新移動(dòng)開發(fā)相關(guān)信息和技術(shù),請(qǐng)關(guān)注mobilehub公眾微信號(hào)(ID: mobilehub)。
總結(jié)
以上是生活随笔為你收集整理的iOS/OS X内存管理(一):基本概念与原理的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Graph Cut and Its Ap
- 下一篇: 开源ImageFilter库v0.3:新