生活随笔
收集整理的這篇文章主要介紹了
初探swift语言的学习笔记六(ARC-自动引用计数,内存管理)
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
作者:fengsh998
原文地址:http://blog.csdn.net/fengsh998/article/details/31824179
轉載請注明出處
如果覺得文章對你有所幫助,請通過留言或關注微信公眾帳號fengsh998來支持我,謝謝!
Swift使用自動引用計數(ARC)來管理應用程序的內存使用。這表示內存管理已經是Swift的一部分,在大多數情況下,你并不需要考慮內存的管理。當實例并不再被需要時,ARC會自動釋放這些實例所使用的內存。
另外需要注意的:
引用計數僅僅作用于類實例上。結構和枚舉是值類型,而非引用類型,所以不能被引用存儲和傳遞。
swift的ARC工作過程
每當創建一個類的實例,ARC分配一個內存塊來存儲這個實例的信息,包含了類型信息和實例的屬性值信息。
另外當實例不再被使用時,ARC會釋放實例所占用的內存,這些內存可以再次被使用。
但是,如果ARC釋放了正在被使用的實例,就不能再訪問實例屬性,或者調用實例的方法了。直接訪問這個實例可能造成應用程序的崩潰。就像空實例或游離實例一樣。
為了保證需要實例時實例是存在的,ARC對每個類實例,都追蹤有多少屬性、常量、變量指向這些實例。當有活動引用指向它時,ARC是不會釋放這個實例的。
為實現這點,當你將類實例賦值給屬性、常量或變量時,指向實例的一個強引用(strong reference)將會被構造出來。被稱為強引用是因為它穩定地持有這個實例,當這個強引用存在時,實例就不能夠被自動釋放,因此可以安全地使用。
例子:
[cpp]?view plaincopy
class?Teacher?? {?? ????var?tName?:?String?? ?? ????init(name:String)?? ????{?? ????????tName?=?name?? ????????println("老師?\(tName)?實例初始化完成.")?? ????}?? ?????? ????func?getName()?->?String?? ????{?? ????????return?tName?? ????}?? ?????? ????func?classing()?? ????{?? ????????println("老師?\(tName)?正在給學生講課.")?? ????}?? ?????? ????deinit?? ????{?? ????????println("老師?\(tName)?實例析構完成.")?? ????}?? }??
測試ARC:
[cpp]?view plaincopy
func?testArc()?? {?? ????var?teacher:Teacher??=?Teacher(name:"張三")???? ????var?refteacher:Teacher??=?teacher?????????????? ????var?refteacher2:Teacher??=?teacher<span?style="white-space:pre">????????</span>?<span?style="font-family:?Arial,?Helvetica,?sans-serif;">?? ?? ?????? ????refteacher?=?nil?????????????? ????teacher?.classing()??????????? ????teacher?=?nil????????????????? ????refteacher2!.classing()??????? ????refteacher2?=?nil????????????? ????refteacher2?.classing()??????? }??
輸出結果:
[cpp]?view plaincopy
老師?張三?實例初始化完成.?? 老師?張三?正在給學生講課.?? 老師?張三?正在給學生講課.?? 老師?張三?實例析構完成.??
從上面的例子來看,確實swift給我們自動管理了內存,很多時侯開發者都不需要考慮太多的內存管理。但真的是這樣嗎?真的安全嗎?作為開發者要如何用好ARC?
盡管ARC減少了很多內存管理工作,但ARC并不是絕對安全的。下面來看一下循環強引用導至的內存泄漏。
例子:
[cpp]?view plaincopy
class?Teacher?? {?? ????var?tName?:?String?? ????var?student?:?Student?????????? ?????? ????init(name:String)?? ????{?? ????????tName?=?name?? ????????println("老師?\(tName)?實例初始化完成.")?? ????}?? ?????? ????func?getName()?->?String?? ????{?? ????????return?tName?? ????}?? ?????? ????func?classing()?? ????{?? ????????println("老師?\(tName)?正在給學生?\(student?.getName())?講課.")?? ????}?? ?????? ????deinit?? ????{?? ????????println("老師?\(tName)?實例析構完成.")?? ????}?? }?? ?? class?Student?? {?? ????var?tName?:?String?? ????var?teacher?:?Teacher?????????? ?????? ????init(name:String)?? ????{?? ????????tName?=?name?? ????????println("學生?\(tName)?實例初始化完成.")?? ????}?? ?????? ????func?getName()?->?String?? ????{?? ????????return?tName?? ????}?? ?????? ????func?listening()?? ????{?? ????????println("學生?\(tName)?正在聽?\(teacher?.getName())?老師講的課")?? ????}?? ?????? ????deinit?? ????{?? ????????println("學生?\(tName)?實例析構化完成.")?? ????}?? }??
測試泄漏:
[cpp]?view plaincopy
func?testMemoryLeak()?? {?? ????var?teacher?:Teacher??? ????var?student?:Student??? ?????? ????teacher?=?Teacher(name:"陳峰")????? ????student?=?Student(name:"徐鴿")????? ?????? ????teacher!.student?=?student???? ????student!.teacher?=?teacher???? ?????? ????teacher!.classing()??????????? ????student!.listening()?? ?????? ?????? ????teacher?=?nil????????????? ????student?=?nil<span?style="white-space:pre">?</span>?????? ?????? ????println("釋放后輸出")?? ?????? ????teacher?.classing()<span?style="white-space:pre">???????</span>?? ????student?.listening()?? ?????? }??
輸出結果:
[cpp]?view plaincopy
老師?陳峰?實例初始化完成.?? 學生?徐鴿?實例初始化完成.?? 老師?陳峰?正在給學生?徐鴿?講課.?? 學生?徐鴿?正在聽?陳峰?老師講的課?? 釋放后輸出??
自始至終都沒有調用deinit。因此就會泄漏,此時已經不能采取任何措拖來釋放這兩個對象了,只有等APP的生命周期結束
實例之間的相互引用,在日常開發中是很常見的一種,哪么如何避免這種循環強引用導致的內存泄漏呢?
可以通過在類之間定義為弱引用(weak)或無宿主引用的(unowned)變量可以解決強引用循環這個問題
弱引用方式:
弱引用并不保持對所指對象的強烈持有,因此并不阻止ARC對引用實例的回收。這個特性保證了引用不成為強引用循環的一部分。指明引用為弱引用是在生命屬性或變量時在其前面加上關鍵字weak。
注意
弱引用必須聲明為變量,指明它們的值在運行期可以改變。弱引用不能被聲明為常量。
因為弱引用可以不含有值,所以必須聲明弱引用為可選類型。因為可選類型使得Swift中的不含有值成為可能。
?因此只需要將上述的例子任意一個實例變量前加上weak關鍵詞即可,如:
[cpp]?view plaincopy
weak?var?student?:?Student??? ?? weak?var?teacher?:?Teacher???
下面來測試一下weak var student : Student?設為弱引用后,測試釋放的時間點(情況一)
[cpp]?view plaincopy
var?teacher?:Teacher??? var?student?:Student??? ?? teacher?=?Teacher(name:"陳峰")?? student?=?Student(name:"徐鴿")?? ?? teacher!.student?=?student???? student!.teacher?=?teacher???? ?? teacher!.classing()?? student!.listening()?? ?? teacher?=?nil????????????????? ?? ?? println("釋放后輸出")?? ?? teacher?.classing()<span?style="white-space:pre">???????</span>?? student?.listening()??????????
經測試輸出:
[cpp]?view plaincopy
老師?陳峰?實例初始化完成.???????????? 學生?徐鴿?實例初始化完成.???????????? 老師?陳峰?正在給學生?徐鴿?講課.???????? 學生?徐鴿?正在聽?陳峰?老師講的課???????? 釋放后輸出????????????????????????? 學生?徐鴿?正在聽?陳峰?老師講的課???????? 學生?徐鴿?實例析構化完成.???????????? 老師?陳峰?實例析構完成.???????????????
如果 ?weak var teacher : Teacher?
再來進行測試:(情況二)
[cpp]?view plaincopy
var?teacher?:Teacher??? var?student?:Student??? ?? teacher?=?Teacher(name:"陳峰")?? student?=?Student(name:"徐鴿")?? ?? teacher!.student?=?student???? student!.teacher?=?teacher???? ?? teacher!.classing()?? student!.listening()?? ?? teacher?=?nil????????????????? ?? ?? println("釋放后輸出")?? ?? teacher?.classing()?? student?.listening()??????????
輸出結果:
[cpp]?view plaincopy
老師?陳峰?實例初始化完成.?? 學生?徐鴿?實例初始化完成.?? 老師?陳峰?正在給學生?徐鴿?講課.?? 學生?徐鴿?正在聽?陳峰?老師講的課?? 老師?陳峰?實例析構完成.?? 釋放后輸出?? 學生?徐鴿?正在聽?nil?老師講的課?? 學生?徐鴿?實例析構化完成.??
經測試得出結論:
當A類中包函有B類的弱引用的實例,同時,B類中存在A的強引用實例時,如果A釋放,也不會影響B的析放,但A的內存回收要等B的實例釋放后才可以回收。(情況一的結果)
當A類中包函有B類的強引用的實例時,如果A釋放,則不會影響B的析放。(情況二的結果)
無宿主引用方式:
和弱引用一樣,無宿主引用也并不持有實例的強引用。但和弱引用不同的是,無宿主引用通常都有一個值。因此,無宿主引用并不定義成可選類型。指明為無宿主引用是在屬性或變量聲明的時候在之前加上關鍵字unowned。
因為無宿主引用為非可選類型,所以每當使用無宿主引用時不必使用?。無宿主引用通??梢灾苯釉L問。但是當無宿主引用所指實例被釋放時,ARC并不能將引用值設置為nil,因為非可選類型不能設置為nil。
注意
在無宿主引用指向實例被釋放后,如果你想訪問這個無宿主引用,將會觸發一個運行期錯誤(僅當能夠確認一個引用一直指向一個實例時才使用無宿主引用)。在Swift中這種情況也會造成應用程序的崩潰,會有一些不可預知的行為發生。因此使用時需要特別小心。
將前面例子改為無宿主引用:
[cpp]?view plaincopy
class?Teacher?? {?? ????var?tName?:?String?? ????var?student?:?Student????????????????? ?????? ????init(name:String)?? ????{?? ????????tName?=?name?? ????????println("老師?\(tName)?實例初始化完成.")?? ????}?? ?????? ????func?getName()?->?String?? ????{?? ????????return?tName?? ????}?? ?????? ????func?classing()?? ????{?? ????????println("老師?\(tName)?正在給學生?\(student?.getName())?講課.")?? ????}?? ?????? ????deinit?? ????{?? ????????println("老師?\(tName)?實例析構完成.")?? ????}?? }?? ?? class?Student?? {?? ????var?tName?:?String?? ????unowned?var?teacher?:?Teacher????????????? ?????? ????init(name:String,tcher?:Teacher)?? ????{?? ????????tName?=?name?? ????????teacher?=?tcher?????? ????????println("學生?\(tName)?實例初始化完成.")?? ????}?? ?????? ????func?getName()?->?String?? ????{?? ????????return?tName?? ????}?? ?????? ????func?listening()?? ????{?? ????????println("學生?\(tName)?正在聽?\(teacher.getName())?老師講的課")?? ????}?? ?????? ????deinit?? ????{?? ????????println("學生?\(tName)?實例析構化完成.")?? ????}?? }??
測試無宿主引用:
[cpp]?view plaincopy
func?testNotOwner()?? {?? ????var?teacher?:Teacher?????????????????? ?????? ????teacher??=?Teacher(name:"陳峰")?? ?????? ????var?student?=?Student(name:?"徐鴿",tcher:?teacher!)?? ?????? ?????? ????teacher!.student?=?student?? ????student.teacher?=?teacher!?? ?????? ????teacher!.classing()?? ????student.listening()?? ?????? ????teacher?=?nil?? ????println("老師對象釋放后")?? ?????? ????teacher?.classing()?? ????student.listening()??? }??
輸出結果:
[cpp]?view plaincopy
老師?陳峰?實例初始化完成.?? 學生?徐鴿?實例初始化完成.?? 老師?陳峰?正在給學生?徐鴿?講課.?? 學生?徐鴿?正在聽?陳峰?老師講的課?? 老師?陳峰?實例析構完成.?? 老師對象釋放后?? Program?ended?with?exit?code:?9(lldb)????
所以使用無宿主引用時,就需要特別小心,小心別人釋放時,順帶釋放了強引用對象,所以要想別人釋放時不影響到原實例,可以使用弱引用這樣就算nil,也不會影響。
上面介紹了,當某個類中的實例對象如果在整個生命周期中,有某個時間可能會被設為nil的實例,使用弱引用,如果整個生命周期中某一實例,一旦構造,過程中不可能再設為nil的實例變量,通常使用無宿主引用。但時有些時侯,在兩個類中的相互引用屬性都一直有值,并且都不可以被設置為nil。這種情況下,通常設置一個類的實例為無宿主屬性,而另一個類中的實例變量設為的隱式裝箱可選屬性(即!號屬性)
如下面的例子,每位父親都有孩子(沒孩子能叫父親么?),每個孩子都有一個親生父親
[cpp]?view plaincopy
class?Father?? {?? ????let?children?:?Children!?????????????????????? ????let?fathername?:?String?? ????init(name:String,childName:String)?? ????{?? ????????self.fathername?=?name?? ????????self.children?=?Children(name:?childName,fat:self)??? ????}?? ?????? ????deinit?? ????{?? ????????println("father?deinited.")?? ????}?? }?? ?? class?Children?? {?? ????unowned?let?father?:?Father??????????????????? ????let?name?:?String?? ????init(name:String?,fat?:?Father)?? ????{?? ????????self.name?=?name?? ????????self.father?=?fat?? ????}?? ?????? ????deinit?? ????{?? ????????println("children?deinited.")?? ????}?? }??
測試代碼:
[cpp]?view plaincopy
var?fa?=?Father(name:?"王五",childName:?"王八")?? println("\(fa.fathername)?有個小孩叫?\(fa.children.name)")??
輸出結果:
[cpp]?view plaincopy
王五?有個小孩叫?王八?? father?deinited.?? children?deinited.??
同樣可以看到,盡管是循環引用,但還是能正?;厥?。
另外,還有一種情況,當自身的閉包對自身(self) 的強引用,也會導致內存泄漏。
例子:
[cpp]?view plaincopy
class?CpuFactory?? {?? ????let?cpuName?:?String?? ????let?cpuRate?:?Double?? ????init(cpuName:String,rate:Double)?? ????{?? ????????self.cpuName?=?cpuName?? ????????self.cpuRate?=?rate?? ????}?? ?????? ?????? ????@lazy?var?someClosure:?(Int,?String)?->?String?=?{?? ?????????? ????????[unowned?self]?(index:?Int,?stringToProcess:?String)?->?String?in?? ?????????? ?????????? ????????return?"A?\(self.cpuName)"???? ????}?? ?????? ?????? ????@lazy?var?machining:?()?->?String?=?{?? ????????[unowned?self]?in???????? ?????????? ?????????? ????????if?self.cpuRate?>?10?? ????????{?? ????????????return?"\(self.cpuName)?i7?2.5G"?? ????????}?? ????????else?? ????????{?? ????????????return?"\(self.cpuName)?i3?2.0G"?? ????????}?? ????}?? ?????? ?????? ????@lazy?var?machining2?:?(CpuFactory)?->?String?=?{?? ?? ????????[unowned?self]?(cpu:CpuFactory)?->?String?in?? ?????????? ????????if?cpu.cpuRate?>?10?? ????????{?? ????????????return?"\(cpu.cpuName)?i7?2.5G"?? ????????}?? ????????else?? ????????{?? ????????????return?"\(cpu.cpuName)?i3?2.0G"?? ????????}?? ????}?? ?????? ????deinit?? ????{?? ????????println("Cpu?Factroy?is?deinited.")?? ????}?? }??
在這個例子中有三個閉包,分別是帶參,和不帶參,對于帶參的 不能省略[unowned self] (paramers) in操作。否則會編譯不過,另外,書中沒有提到的,只有聲明為@lazy的閉包中才可以使用[unowned self] 否則在普通閉包中使用也會報錯。還有一點書中講到當自身閉包中使用self.時會產生強引用,導至內存泄漏,因此加上[unowned self ] in 這句可以破壞這種強引用,從而使內存得到釋放,但經本人親自驗證,就算加上了也沒有釋放。
測試:
[cpp]?view plaincopy
func?testClosure()?? {?? ????var?cpu?:?CpuFactory??=?CpuFactory(cpuName:?"Core",rate:?5)?? ?? ????println(cpu!.machining2(cpu!))?? ?? ?????? ????cpu?=?nil?? }??
分別單獨驗證各句輸出結果:
[cpp]?view plaincopy
func?testClosure()?? {?? ????var?cpu?:?CpuFactory??=?CpuFactory(cpuName:?"Core",rate:?5)?? ????println(cpu!.machining())?? ????cpu?=?nil?? }??
輸出:
[cpp]?view plaincopy
Core?i3?2.0G??
顯然cpu = nil也不會釋放內存。
再來看第二個。
[cpp]?view plaincopy
func?testClosure()?? {?? ????var?cpu?:?CpuFactory??=?CpuFactory(cpuName:?"Core",rate:?5)?? ????println(cpu!.machining2(cpu!))?? ????cpu?=?nil?? }??
輸出
[cpp]?view plaincopy
Core?i3?2.0G?? Cpu?Factroy?is?deinited.??
可見使用自身作為參數傳參時,可以釋放內存。
同樣再測試第三種:
[cpp]?view plaincopy
func?testClosure()?? {?? ????var?cpu?:?CpuFactory??=?CpuFactory(cpuName:?"Core",rate:?5)?? ????println(cpu!.someClosure(3,"hello"))?? ????cpu?=?nil?? }??
輸出
[cpp]?view plaincopy
A?Core??
其實第三和第一種是一樣的,都是引用了self.但第一種可以把[unowned self ]in ?句注釋和不注釋的情況下進行測試,可以發現結果是一樣的,并沒有釋放內存。
實在令人有點費解。。。。。。
總結
以上是生活随笔為你收集整理的初探swift语言的学习笔记六(ARC-自动引用计数,内存管理)的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。