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ù)庫(kù)提供的功能诊沪,特點(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è)常用的刷新第三方庫(kù)的時(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)存泄漏的問題。
參考文章: