對(duì)于OC的使用者來(lái)說(shuō),最會(huì)被問(wèn)到的就是iOS開(kāi)發(fā)中的內(nèi)存管理摊欠。而只要涉及到內(nèi)存管理丢烘,就肯定會(huì)涉及到property。而且在平常iOS開(kāi)發(fā)的面試中些椒,我們也經(jīng)常會(huì)被問(wèn)到相關(guān)的問(wèn)題播瞳。所以這次就結(jié)合我所見(jiàn)到的和自己對(duì)于屬性的理解來(lái)進(jìn)行簡(jiǎn)述,希望對(duì)你們有幫助免糕。
在講述屬性前赢乓,我們需要對(duì)于iOS開(kāi)發(fā)中的內(nèi)存管理有一個(gè)初步的了解忧侧。
本文中的部分內(nèi)容來(lái)源于:這里和這里還有部分來(lái)源于蘋果官方文檔。
iOS開(kāi)發(fā)中的內(nèi)存使用情況
- 棧(stack):棧是編譯器自動(dòng)分配并釋放骏全,用來(lái)存放函數(shù)的參數(shù)苍柏,局部變量。
- 堆(heap):堆一般是程序員自己分配和釋放姜贡,如果我們?cè)谑褂玫倪^(guò)程中试吁,沒(méi)有釋放,那么等到程序完全結(jié)束楼咳,系統(tǒng)將會(huì)對(duì)堆中的內(nèi)容進(jìn)行回收熄捍。一般開(kāi)發(fā)中的alloc就是存放在堆中的
- 全局變量(靜態(tài)變量)(static):全局變量和靜態(tài)變量是單獨(dú)存放的,因?yàn)樗麄兊穆暶髦芷诤驼麄€(gè)程序的生命周期一致母怜。釋放的時(shí)間是由整個(gè)程序結(jié)束后系統(tǒng)負(fù)責(zé)回收余耽。
- 文字常量區(qū):一般用來(lái)存放常量字符串,比如說(shuō)
String *str = @"hello world"
,他的釋放也和全局變量一致苹熏,在程序結(jié)束后進(jìn)行釋放碟贾。 - 程序代碼區(qū):用來(lái)存放函數(shù)的二進(jìn)制內(nèi)容的區(qū)域。
他們之間的在內(nèi)存中存放的關(guān)系如下圖所示:
而在日常開(kāi)發(fā)過(guò)程中轨域,我們所遇到最多的也是最容易問(wèn)到的就是堆和棧袱耽,可能有的人對(duì)于這兩者還是很模糊,所以我再引用之前看到過(guò)的一段話來(lái)解釋(由于很久以前看到了干发,已經(jīng)忘記了出處朱巨,如果有人能夠告訴我,我一定加上):
內(nèi)管管理就像做菜枉长,而棧(stack)的使用就像我們下館子冀续,我們只要去酒店,告訴老板我們要什么必峰,他們就會(huì)幫我們準(zhǔn)備好洪唐,我們只要吃完付錢,就可以了吼蚁,也不用去管理那些之后的瑣事桐罕。而堆(heap)就像我們自己做菜,什么東西都得自己買好桂敛,做什么功炮,怎么做都得提前想好。做完之后還需要自己收拾最后的垃圾术唬。一般我們alloc一個(gè)對(duì)象薪伏,該對(duì)象的內(nèi)存就會(huì)被分配到堆上,而非對(duì)象創(chuàng)建的時(shí)候就會(huì)放到棧上粗仓,由系統(tǒng)在不需要的時(shí)候進(jìn)行回收嫁怀。
iOS內(nèi)存管理機(jī)制
當(dāng)你創(chuàng)建的對(duì)象的時(shí)候设捐,你總得需要告訴操作系統(tǒng),你將要在什么時(shí)候?qū)φ加羞@塊內(nèi)存的對(duì)象進(jìn)行釋放塘淑,就像平常生活中萝招,你怎么才能確定這房間里面是有人的還是沒(méi)有人的呢?
一般來(lái)說(shuō)存捺,我們肯定會(huì)這么干槐沼,每個(gè)人進(jìn)去的時(shí)候登記下,然后出來(lái)的時(shí)候再登記下捌治,這樣只要我們?cè)谛枰芾淼臅r(shí)候看下登記再按的人數(shù)是不是為0就可以了岗钩,只要為0,那么就說(shuō)明里面沒(méi)有人肖油,否則就有人兼吓,蘋果也是這么處理內(nèi)存管理的。在你創(chuàng)建一個(gè)對(duì)象的時(shí)候森枪,他會(huì)需要進(jìn)行retain
视搏,然后在你不在持有他的時(shí)候進(jìn)行release
,所以每個(gè)對(duì)象都有一個(gè)retain count
來(lái)進(jìn)行計(jì)數(shù)县袱。
在iOS中有2套內(nèi)存管理機(jī)制:MRC(MannulReference Counting)和ARC(Automatic Reference Counting)凶朗。其中ARC起源于iOS 4.3,在那之前显拳,蘋果開(kāi)發(fā)者只能手動(dòng)使用retain
和release
來(lái)進(jìn)行內(nèi)存管理。這樣做的問(wèn)題很明顯搓萧,如果你在開(kāi)發(fā)過(guò)程中杂数,一個(gè)不小心沒(méi)有retain
和release
成對(duì)出現(xiàn),那么很容易使得內(nèi)存沒(méi)有釋放瘸洛,最后導(dǎo)致程序內(nèi)存不足而導(dǎo)致閃退揍移。
對(duì)于ARC,蘋果的官方文檔是這么解釋的:
Automatic Reference Counting (ARC) is a compiler feature that provides automatic memory management of Objective-C objects. Rather than having to think about retain and release operations, ARC allows you to concentrate on the interesting code, the object graphs, and the relationships between objects in your application.
ARC works by adding code at compile time to ensure that objects live as long as necessary, but no longer. Conceptually, it follows the same memory management conventions as manual reference counting (described in Advanced Memory Management Programming Guide) by adding the appropriate memory management calls for you.
總的來(lái)說(shuō)反肋,主要意思就是ARC在本質(zhì)上就像MRC一樣那伐,但是它能夠讓你花費(fèi)更少的時(shí)間來(lái)考慮代碼中的retain
和release
。與此同時(shí)石蔗,編譯器能夠幫你更加準(zhǔn)確的將retain
和release
加在你代碼中真正需要的地方罕邀。
理解如下圖:
有人可能會(huì)提到GC(Garbage Collection)——垃圾回收機(jī)制。在現(xiàn)在的iOS中GC沒(méi)有被使用养距,而在MAC OS X中诉探,GC是被使用的,不過(guò)對(duì)于GC棍厌,蘋果是這么解釋的:
Garbage collection is deprecated in OS X Mountain Lion v10.8, and will be removed in a future version of OS X. Automatic Reference Counting is the recommended replacement technology. To aid in migrating existing applications, the ARC migration tool in Xcode 4.3 and later supports migration of garbage collected OS X applications to ARC
總的來(lái)說(shuō)ARC將在未來(lái)的某一天來(lái)取代GC在MAC中的位置肾胯。
property(屬性)
就算理解的再多竖席,我們還是要和實(shí)際相結(jié)合。實(shí)際運(yùn)用中與之相對(duì)應(yīng)的就是實(shí)例變量的屬性敬肚。
由于 iOS 中 MRC已經(jīng)太過(guò)于古老毕荐,在這就不再多提。
在ARC中艳馒,我們創(chuàng)建實(shí)例變量經(jīng)常是這樣的
@property (atomic/nonatomic/assign/retain/strong/weak/unsafe_unretained/copy) Number* num
所以我們就為讓這內(nèi)容中的這幾個(gè)property來(lái)進(jìn)行解釋憎亚。
- atomic:原子性,簡(jiǎn)單的解釋就是說(shuō)鹰溜,他是線程安全的虽填,但是由于線程安全,在操作的過(guò)程中曹动,編譯器會(huì)自己給他上鎖斋日,解鎖。這樣會(huì)造成資源的浪費(fèi)(因?yàn)槲覀兤匠i_(kāi)發(fā)中不會(huì)那么頻繁的考慮到線程安全這個(gè)問(wèn)題)該屬性為默認(rèn)值
- nonatomic:非原子性墓陈,這個(gè)和上面那個(gè)是雙子星恶守,但是他們正好相反,這個(gè)由于不安全的贡必,但是因?yàn)闆](méi)有了鎖的問(wèn)題兔港,這樣資源就會(huì)盡可能的被利用,所以我們?cè)谌粘i_(kāi)發(fā)中使用這個(gè)仔拟,而對(duì)于他的安全性衫樊,我們一般都自己在后期開(kāi)發(fā)的過(guò)程中,在使用多線程的過(guò)程中進(jìn)行考慮利花。
- assign:給予了setter方法科侈,assign一般指向一個(gè)不是指針指向的對(duì)象,比如說(shuō)
CGFloat
這類值炒事。(關(guān)于assign
和weak
在delegate中的區(qū)別將會(huì)在后面提到) - retain:
retain
用于指向一個(gè)有指針的對(duì)象臀栈,就像MRC中的retain,他是為了增加retain count
挠乳,這樣使得對(duì)象能夠在autoreleasepool
中持有內(nèi)存 - strong:
strong
是在ARC中用來(lái)代替retain
的权薯,所以原理相同,即retain count
加一 - weak:
weak
和strong
一般只要懂一點(diǎn)英語(yǔ)的就知道睡扬,他們又是一堆雙子星盟蚣,strong
是表示持有,而weak
表示卖怜,只是簡(jiǎn)單的引用刁俭。這意味著等到持有weak
的對(duì)象唄釋放的時(shí)候,weak
表示的對(duì)象也被釋放韧涨。而需要注意的是weak
釋放后他就會(huì)為nil牍戚。 - unsafe_unretained:
unsafe_unretained
一般很少被用到侮繁,主要的原因是他一般用于在Cocoa底層的那些不能支持weak
屬性的變量,比如說(shuō)NSTextView
如孝,NSFont
宪哩,NSColorSpace
等。而他和weak不同的地方是第晰,持有weak
的對(duì)象被釋放后锁孟,weak
對(duì)象會(huì)被指向nil,而unsafe_unretained
則不會(huì)被指向?yàn)閚il茁瘦。這樣就有了安全隱患品抽。 - copy:
copy
一般我們用在NSString
,NSArray
甜熔,NSDictionary
圆恤,而原因就是copy會(huì)在復(fù)制的時(shí)候講源對(duì)象進(jìn)行拷貝。這樣他只想的將會(huì)是一個(gè)新的腔稀,retain count
為1的對(duì)象盆昙。這樣在我們對(duì)于這個(gè)對(duì)象的內(nèi)容進(jìn)行修改的時(shí)候,它將不會(huì)影響到原來(lái)的那個(gè)對(duì)象焊虏。
除了這幾種之外淡喜,我們還有4種屬性沒(méi)有提到
- getter:設(shè)置getter方法
- setter:設(shè)置setter方法
- readonly:只讀
- readwrite:可讀寫
這四種因?yàn)榭梢愿鶕?jù)字面意思來(lái)進(jìn)行設(shè)置在這就不進(jìn)行過(guò)多的介紹。
這里再補(bǔ)上一句之前提到的問(wèn)題:
關(guān)于為什么使用
weak
而不是用assign
來(lái)對(duì)delegate進(jìn)行標(biāo)注诵闭。
首先delegate一般的類型都是id炼团,即可以指向所有對(duì)象的id
類型。所以我們既可以使用assign指向他疏尿,也可以使用weak指向他瘟芝。但是因?yàn)閣eak在不被持有的時(shí)候會(huì)指向nil,而大家都知道润歉,所有通過(guò)nil的方法在調(diào)用函數(shù)的時(shí)候,都會(huì)返回為nil颈抚,這樣就能夠保證了程序的穩(wěn)定性踩衩,就算沒(méi)有東西返回,他還是能夠正常解析(只是解析出來(lái)的值為nil)贩汉。而如果使用assign的話驱富,如果一不持有他。那么下次再調(diào)用它的時(shí)候匹舞,他將會(huì)指向一個(gè)不知名的地址褐鸥,即野指針。這樣就會(huì)使得整個(gè)程序crash赐稽。
autoreleasepool(自動(dòng)釋放池)
既然扯到release叫榕,那就不得不提下autoreleasepool浑侥,顧名思義,他就是一個(gè)用來(lái)自動(dòng)幫你釋放的池子(這特么不是廢話么)晰绎。一般情況下我們不怎么會(huì)去使用它寓落,因?yàn)樵贏ppKit和UIKit的框架中,事情基本上默認(rèn)的放在autorelease pool block
中完成的荞下。這樣子當(dāng)你完成這些內(nèi)容后伶选,對(duì)應(yīng)在內(nèi)存中的數(shù)據(jù)就會(huì)自動(dòng)釋放,這樣就不需要你手動(dòng)去處理這些數(shù)據(jù)尖昏,從而保證了程序的安全性仰税。
當(dāng)然這里也提到了這是在使用AppKit和UIKit的情況下。我們?nèi)匀挥幸韵逻@幾種情況來(lái)使用autoreleasepool的情況:
- 編寫的是命令行程序抽诉,不基于UI框架
- 當(dāng)你需要寫一個(gè)循環(huán)陨簇,循環(huán)里面有很多臨時(shí)變量的時(shí)候
- 當(dāng)你大量使用輔助線程
總的來(lái)說(shuō)autoreleasepool是為了盡可能的減少無(wú)用變量在內(nèi)存中的占用情況。從而使得程序所需要的內(nèi)存更少掸鹅。至于autoreleasepool的釋放時(shí)間塞帐,這就要涉及到runloop,具體內(nèi)容可以參見(jiàn)sunnyxx大神的這篇文章巍沙,相信對(duì)你肯定有很大幫助葵姥。
循環(huán)引用
循環(huán)引用對(duì)于內(nèi)存來(lái)說(shuō)是一個(gè)大問(wèn)題。這個(gè)問(wèn)題我們更多的在block中可能會(huì)碰到句携,不過(guò)總結(jié)起來(lái)就是這么一句話:
A對(duì)象持有B榔幸,那么B的
retain count
為1,而此時(shí)A因?yàn)楸黄渌兞砍钟邪担蠥的retain Count
為1削咆,而B(niǎo)又要引用A,那么A的retain count
又要加一蠢笋,這樣等到持有A的那個(gè)對(duì)象釋放A的時(shí)候拨齐,就算A的retain count
減一,但還是為1昨寞,所以無(wú)法釋放瞻惋,而因?yàn)锳無(wú)法釋放,導(dǎo)致B也無(wú)法成功釋放援岩,而外部沒(méi)有持有A或B中的任意一個(gè)歼狼,這樣就導(dǎo)致了這塊內(nèi)存空間一直被持有。
圖示如下:
具體的問(wèn)題我將會(huì)在解釋block的時(shí)候具體說(shuō)明享怀,需要知道的一點(diǎn)就是羽峰,如果存在循環(huán)引用,那么就需要把循環(huán)引用中間的一條線斷掉,從而使用weak來(lái)代替梅屉,使得當(dāng)strong的一方被釋放的時(shí)候值纱,weak也能被正常釋放。
希望以上文章能夠?qū)δ阌兴鶐椭?/p>