Objective-C,顧名思義,是一門超C的語言,自從ARC(Auto Reference Count)出現(xiàn)了之后,我們就很少會(huì)去關(guān)注關(guān)于內(nèi)存管理這方面的事情了,這些功能的設(shè)計(jì)者和實(shí)現(xiàn)者們?yōu)榇烁冻龅呐χ档梦覀兎Q贊,但是如果我們對(duì)此不知不顧的話,一旦出現(xiàn)了內(nèi)存泄漏的問題的話,也是個(gè)坑,所以先去嘗試著去了解它,對(duì)我們也不失是一件好事。下面是c語言中的內(nèi)存分配方式:
棧區(qū)(stack):棧又稱堆棧, 是用戶存放程序臨時(shí)創(chuàng)建的局部變量,也就是說我們函數(shù)括弧“{}”中定義的變量。除此以外,在函數(shù)被調(diào)用時(shí),其參數(shù)也會(huì)被壓入發(fā)起調(diào)用的進(jìn)程棧中,并且待到調(diào)用結(jié)束后,函數(shù)的返回值也會(huì)被存放回棧中。由于棧的先進(jìn)先出特點(diǎn),所以棧特別方便用來保存/恢復(fù)調(diào)用現(xiàn)場(chǎng)。從這個(gè)意義上講,我們可以把堆??闯梢粋€(gè)寄存、交換臨時(shí)數(shù)據(jù)的內(nèi)存區(qū)。棧內(nèi)存分配運(yùn)算內(nèi)置于處理器的指令集,效率很高,但是分配的內(nèi)存容量有限,比如iOS中棧區(qū)的大小是2M。(棧系統(tǒng)提供的功能,特點(diǎn)是快速高效,缺點(diǎn)是有限制,數(shù)據(jù)不靈活;而堆是函數(shù)庫提供的功能,特點(diǎn)是靈活方便,數(shù)據(jù)適應(yīng)面廣泛,但是效率有一定降低。)
堆區(qū)(heap):就是通過new、malloc、realloc分配的內(nèi)存塊,它們的釋放編譯器不去管,由我們的應(yīng)用程序去釋放。如果應(yīng)用程序沒有釋放掉,操作系統(tǒng)會(huì)自動(dòng)回收。分配方式類似于鏈表。
靜態(tài)區(qū):全局變量和靜態(tài)變量的存儲(chǔ)是放在一塊的,初始化的全局變量和靜態(tài)變量在一塊區(qū)域,未初始化的全局變量和未初始化的靜態(tài)變量在相鄰的另一塊區(qū)域。程序結(jié)束后,由系統(tǒng)釋放。
常量區(qū):常量存儲(chǔ)在這里,不允許修改的。
代碼區(qū):存放函數(shù)體的二進(jìn)制代碼。
iOS開發(fā)中,數(shù)據(jù)類型有兩種,一種是int 、float等基本數(shù)據(jù)類型,而基本數(shù)據(jù)類型是放在棧上,由系統(tǒng)進(jìn)行管理,還有一種就是對(duì)象,存儲(chǔ)在堆上,如果一個(gè)對(duì)象創(chuàng)建并使用后并沒有得到及時(shí)的釋放,就會(huì)一直占用著內(nèi)存,當(dāng)這樣的對(duì)象一直占據(jù)著內(nèi)存得不到釋放,這就是我們常說的內(nèi)存泄漏,嚴(yán)重的話會(huì)導(dǎo)致程序崩潰。Objective-C 中的內(nèi)存管理,其實(shí)主要是講的在堆區(qū)上面對(duì)內(nèi)存的管理,而iOS的內(nèi)存管理機(jī)制就是引用計(jì)數(shù)(reference counting):
1)每個(gè)對(duì)象都有一個(gè)關(guān)聯(lián)的整數(shù),稱為引用計(jì)數(shù)器
2)當(dāng)代碼需要使用該對(duì)象時(shí),則將對(duì)象的引用計(jì)數(shù)加1
3)當(dāng)代碼結(jié)束使用該對(duì)象時(shí),則將對(duì)象的引用計(jì)數(shù)減1
4)當(dāng)引用計(jì)數(shù)的值變?yōu)?時(shí),表示對(duì)象沒有被任何代碼使用,此時(shí)對(duì)象將被釋放。
其實(shí)對(duì)應(yīng)的也可以用開關(guān)房間的燈為例來說明引用計(jì)數(shù)的機(jī)制。
1)當(dāng)?shù)谝粋€(gè)人進(jìn)入辦公室時(shí),他需要使用燈,于是開燈,引用計(jì)數(shù)為1
2)每當(dāng)多一個(gè)人進(jìn)入辦公室時(shí),引用計(jì)數(shù)加1
3)當(dāng)有一個(gè)人離開辦公室時(shí),引用計(jì)數(shù)減1,
4)當(dāng)引用計(jì)數(shù)為0時(shí),也就是最后一個(gè)人離開辦公室時(shí),他不再需要使用燈,關(guān)燈離開辦公室。(釋放對(duì)象)
1. Objective-c語言中的MRC(MannulReference Counting)
在MRC的內(nèi)存管理模式下,與對(duì)變量的管理相關(guān)的方法有:retain,release和autorelease。retain和release方法操作的是引用記數(shù),當(dāng)引用記數(shù)為零時(shí),便自動(dòng)釋放內(nèi)存。并且可以用NSAutoreleasePool對(duì)象,對(duì)加入自動(dòng)釋放池(autorelease調(diào)用)的變量進(jìn)行管理,當(dāng)drain時(shí)回收內(nèi)存。
1)retain,retain是一個(gè)實(shí)例方法,只能由對(duì)象調(diào)用,它的作用是使這個(gè)對(duì)象的內(nèi)存空間的引用計(jì)數(shù)加1,并不會(huì)新開辟一塊內(nèi)存空間,通常于賦值是調(diào)用,該方法的作用是將內(nèi)存數(shù)據(jù)的所有權(quán)附給另一指針變量,引用數(shù)加1,即retainCount+= 1 ; 如:對(duì)象2=[對(duì)象1 retain];表示對(duì)象2同樣擁有這塊內(nèi)存的所有權(quán)。若只是簡(jiǎn)單地賦值,如:對(duì)象2=對(duì)象1;那么當(dāng)對(duì)象1的內(nèi)存空間被釋放的時(shí)候,對(duì)象2便會(huì)成為野指針,再對(duì)對(duì)象2進(jìn)行操作便會(huì)造成內(nèi)存錯(cuò)誤。
2)release,該方法是釋放指針變量對(duì)內(nèi)存數(shù)據(jù)的所有權(quán),引用數(shù)減1,即retainCount-= 1,若引用計(jì)數(shù)變?yōu)?則系統(tǒng)會(huì)立刻釋放掉這塊內(nèi)存。如果引用計(jì)數(shù)為0的基礎(chǔ)上再調(diào)用release,便會(huì)造成過度釋放,使內(nèi)存崩潰;
3)autorelease,:autorelease是一個(gè)實(shí)例方法,同樣只能由對(duì)象調(diào)用,它的作用于release類似,但不是立刻減1,相當(dāng)于一個(gè)延遲的release,通常用于方法返回值的釋放,如便利構(gòu)造器。autorelease會(huì)在程序走出自動(dòng)釋放池時(shí)執(zhí)行,通常系統(tǒng)會(huì)自動(dòng)生成自動(dòng)釋放池(即使是MRC下),也可以自己設(shè)定自動(dòng)釋放池,如:
@autoreleasepool{
? ? ? ? ?obj= [[NSObject alloc]init];
? ? ? ? ?[obj autorelease];
}
當(dāng)程序走出“}”時(shí)obj的引用計(jì)數(shù)就會(huì)減1.
當(dāng)然自動(dòng)釋放池還有一種寫法
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc]init];
//這里寫代碼
[pool release];
但是使用自動(dòng)釋放池需要注意:
1)自動(dòng)釋放池實(shí)質(zhì)上只是在釋放的時(shí)候給池中所有對(duì)象對(duì)象發(fā)送release消息,不保證對(duì)象一定會(huì)銷毀,如果自動(dòng)釋放池向?qū)ο蟀l(fā)送release消息后對(duì)象的引用計(jì)數(shù)仍大于1,對(duì)象就無法銷毀。
2)自動(dòng)釋放池中的對(duì)象會(huì)集中同一時(shí)間釋放,如果操作需要生成的對(duì)象較多占用內(nèi)存空間大,可以使用多個(gè)釋放池來進(jìn)行優(yōu)化。比如在一個(gè)循環(huán)中需要?jiǎng)?chuàng)建大量的臨時(shí)變量,可以創(chuàng)建內(nèi)部的池子來降低內(nèi)存占用峰值。
3)autorelease不會(huì)改變對(duì)象的引用計(jì)數(shù)
在mrc中程序員們要遵循下面的基本原則:
1)當(dāng)你通過new、alloc、copy或mutableCopy方法創(chuàng)建一個(gè)對(duì)象時(shí),它的引用計(jì)數(shù)為1,當(dāng)不再使用該對(duì)象時(shí),應(yīng)該向?qū)ο蟀l(fā)送release或者autorelease消息釋放對(duì)象。
2)當(dāng)你通過其他方法獲得一個(gè)對(duì)象時(shí),如果對(duì)象引用計(jì)數(shù)為1且被設(shè)置為autorelease,則不需要執(zhí)行任何釋放對(duì)象的操作;
3)如果你打算取得對(duì)象所有權(quán),就需要保留對(duì)象并在操作完成之后釋放,且必須保證retain和release的次數(shù)對(duì)等。
簡(jiǎn)單得來說就是誰分配誰釋放。
2. Objective-c語言中的ARC下的內(nèi)存管理(Automatic Reference Counting)
ARC是iOS 5推出的新功能,全稱叫 ARC(Automatic Reference Counting)。簡(jiǎn)單地說,就是代碼中自動(dòng)加入了retain/release,原先需要手動(dòng)添加的用來處理內(nèi)存管理的引用計(jì)數(shù)的代碼可以自動(dòng)地由編譯器完成了。該機(jī)能在 iOS 5/ Mac OS X 10.7 開始導(dǎo)入,利用 Xcode4.2 可以使用該機(jī)能。簡(jiǎn)單地理解ARC,就是通過指定的語法,讓編譯器(LLVM 3.0)在編譯代碼時(shí),自動(dòng)生成實(shí)例的引用計(jì)數(shù)管理部分代碼。有一點(diǎn),ARC并不是GC,它只是一種代碼靜態(tài)分析(Static Analyzer)工具。
使用ARC有什么好處呢?
其實(shí)大家都知道,自從有了arc之后寫Objective-C的代碼變得簡(jiǎn)單多了,因?yàn)槲覀儾恍枰獡?dān)心煩人的內(nèi)存管理,擔(dān)心內(nèi)存泄露了
代碼的總量變少了,看上去清爽了不少,也節(jié)省了勞動(dòng)力
代碼高速化,由于使用編譯器管理引用計(jì)數(shù),減少了低效代碼的可能性
但是實(shí)質(zhì)上ARC只能解決iOS開發(fā)中的90%的內(nèi)存管理問題,還有10%的內(nèi)存管理是需要程序員自己處理的,主要是循環(huán)引用和對(duì)底層Core Foundatin的調(diào)用。
循環(huán)引用,其實(shí)簡(jiǎn)單得來講就是對(duì)象A和對(duì)象B,互相引用對(duì)方作為自己的成員變量,但是由于引用計(jì)數(shù)的管理方式,只有對(duì)象自己銷毀的時(shí)候,才會(huì)將成員變量的引用計(jì)數(shù)減1,然而對(duì)象A和對(duì)象B由于都引用了對(duì)方,都得不到銷毀,這樣的情況就是循環(huán)引用。當(dāng)然這只是循環(huán)引用的一種情況,多個(gè)對(duì)象依次持有對(duì)方,形成一個(gè)環(huán)狀,也可能造成循環(huán)引用,一般來說,環(huán)越大就越難被發(fā)現(xiàn)。
一般來說,解決循環(huán)引用的方法有兩個(gè)
1)第一個(gè)方法是我知道這里會(huì)造成循環(huán)引用,但是我在使用完了這個(gè)功能、方法之后我主動(dòng)去破除這個(gè)循環(huán)引用的情況,主動(dòng)斷開循環(huán)引用這種操作依賴于程序員自己手工顯式地控制,相當(dāng)于回到了以前 “誰申請(qǐng)誰釋放” 的內(nèi)存管理年代,它依賴于程序員自己有能力發(fā)現(xiàn)循環(huán)引用并且知道在什么時(shí)機(jī)斷開循環(huán)引用回收內(nèi)存(這通常與具體的業(yè)務(wù)邏輯相關(guān)),所以這種解決方法并不常用,更常見的辦法是使用弱引用 (weak reference) 的辦法。
2)弱引用雖然持有對(duì)象,但是并不增加引用計(jì)數(shù),這樣就避免了循環(huán)引用的產(chǎn)生。在 iOS 開發(fā)中,弱引用通常在 delegate 模式中使用。弱引用的實(shí)現(xiàn)原理是這樣,系統(tǒng)對(duì)于每一個(gè)有弱引用的對(duì)象,都維護(hù)一個(gè)表來記錄它所有的弱引用的指針地址。這樣,當(dāng)一個(gè)對(duì)象的引用計(jì)數(shù)為 0 時(shí),系統(tǒng)就通過這張表,找到所有的弱引用指針,繼而把它們都置成 nil。一般在block中使用不當(dāng)也會(huì)造成循環(huán)引用,例如在使用MJRefresh這個(gè)常用的刷新第三方庫的時(shí)候,要使用__weak 來實(shí)現(xiàn)弱引用,避免循環(huán)引用
__weak typeof(self) weakself = self;
self.table.mj_header = [MJRefreshGifHeader headerWithRefreshingBlock:^{
? ? ? ?weakself.page = 1;
? ? ? ?weakself.table.mj_footer.hidden = YES;
? ? ? ?[weakself sendRequest];
}];
Core Foundation 對(duì)象的內(nèi)存管理
下面我們就來簡(jiǎn)單介紹一下對(duì)底層 Core Foundation 對(duì)象的內(nèi)存管理。底層的 Core Foundation 對(duì)象,在創(chuàng)建時(shí)大多以 XxxCreateWithXxx 這樣的方式創(chuàng)建,例如:
// 創(chuàng)建一個(gè) CFStringRef 對(duì)象
CFStringRef str= CFStringCreateWithCString(kCFAllocatorDefault, “hello world", kCFStringEncodingUTF8);
// 創(chuàng)建一個(gè) CTFontRef 對(duì)象
CTFontRef fontRef = CTFontCreateWithName((CFStringRef)@"ArialMT", fontSize, NULL);
對(duì)于這些對(duì)象的引用計(jì)數(shù)的修改,要相應(yīng)的使用 CFRetain 和 CFRelease 方法。如下所示:
// 創(chuàng)建一個(gè) CTFontRef 對(duì)象
CTFontRef fontRef = CTFontCreateWithName((CFStringRef)@"ArialMT", fontSize, NULL);
// 引用計(jì)數(shù)加 1
CFRetain(fontRef);
// 引用計(jì)數(shù)減 1
CFRelease(fontRef);
對(duì)于 CFRetain 和 CFRelease 兩個(gè)方法,讀者可以直觀地認(rèn)為,這與 Objective-C 對(duì)象的 retain 和 release 方法等價(jià)。
所以對(duì)于底層 Core Foundation 對(duì)象,我們只需要延續(xù)以前手工管理引用計(jì)數(shù)的辦法即可。
除此之外,還有另外一個(gè)問題需要解決。在 ARC 下,我們有時(shí)需要將一個(gè) Core Foundation 對(duì)象轉(zhuǎn)換成一個(gè) Objective-C 對(duì)象,這個(gè)時(shí)候我們需要告訴編譯器,轉(zhuǎn)換過程中的引用計(jì)數(shù)需要做如何的調(diào)整。這就引入了bridge相關(guān)的關(guān)鍵字,以下是這些關(guān)鍵字的說明:
__bridge: 只做類型轉(zhuǎn)換,不修改相關(guān)對(duì)象的引用計(jì)數(shù),原來的 Core Foundation 對(duì)象在不用時(shí),需要調(diào)用 CFRelease 方法。
__bridge_retained:類型轉(zhuǎn)換后,將相關(guān)對(duì)象的引用計(jì)數(shù)加 1,原來的 Core Foundation 對(duì)象在不用時(shí),需要調(diào)用 CFRelease 方法。
__bridge_transfer:類型轉(zhuǎn)換后,將該對(duì)象的引用計(jì)數(shù)交給 ARC 管理,Core Foundation 對(duì)象在不用時(shí),不再需要調(diào)用 CFRelease 方法。
我們根據(jù)具體的業(yè)務(wù)邏輯,合理使用上面的 3 種轉(zhuǎn)換關(guān)鍵字,就可以解決 Core Foundation 對(duì)象與 Objective-C 對(duì)象相對(duì)轉(zhuǎn)換的問題了。
在項(xiàng)目中,其實(shí)我們也可以用Instruments來檢測(cè)我們項(xiàng)目的內(nèi)存泄漏的問題。
參考文章: