ARC(自動引用計數)是Objective-C和Swift中用於解決內存管理問題的方案。在學習Objective-C編程時常常會學習到一個關於ARC的例子:在一個公用的圖書館中,每次進入一人就將卡插入,走的時候將本身的卡拔出拿走。圖書館系統會斷定只要有卡插入,就將圖書館的燈打開,當全部卡都被取走後,將圖書館的燈關掉。這個例子對應於Objective-C中的對象聲明週期管理十分貼切。每當一個對象增長一個引用時,其引用計數會加1,當一個引用被取消時,對象的引用計數減1,當引用計數減爲0時,說明此對象將再也不有任何引用,對象會被釋放掉,讓出內存。Swift也採用一樣的方式進行內存管理。javascript
注意:在Swift中只有引用類型有自動引用計數,結構體、枚舉這類值類型是沒有引用計數的。關於引用計數的示例代碼以下:java
class MyClass { deinit{ print("MyClass deinit") } } var cls1:MyClass? = MyClass() var cls2:MyClass? = cls1 var cls3:MyClass? = cls2 cls2 = nil cls1 = nil //執行下面代碼後纔會打印「MyClass deinit」 cls3 = nil
在開發中,開發者一不當心就會寫出產生循環引用的代碼,在上面的示例中能夠看出,除非實例的引用所有解除,不然實例將不會調用析構方法,內存不會被釋放,若是在寫代碼時,A引用了B,一樣B也引用了A,那麼實際上如今A和B的引用計數都是2,將A和B都置爲nil後,A和B實例依然保有1個引用計數,都不會被釋放,實例以下:編程
class MyClassOne { var cls:MyClassTwo? deinit{ print("ClassOne deinit") } } class MyClassTwo { var cls:MyClassOne? deinit{ print("ClassTwo deinit") } } var obj1:MyClassOne? = MyClassOne() var obj2:MyClassTwo? = MyClassTwo() obj1?.cls = obj2 obj2?.cls = obj1 obj1=nil obj2=nil //沒有打印析構函數的調用信息
對於上面的狀況,能夠將屬性聲明稱weak類型來防止這種循環引用,weak的做用在於只是弱引用實例,原實例的引用計數並不會加1,示例以下:閉包
//關於弱引用的演示 class MyClassThree{ weak var cls:MyClassFour? deinit{ print("ClassThree deinit") } } class MyClassFour { var cls:MyClassThree? deinit{ print("ClassFour deinit") } } var obj3:MyClassThree? = MyClassThree() var obj4:MyClassFour? = MyClassFour() obj3?.cls = obj4 obj4?.cls = obj3 obj4=nil //此時obj3中的cls也爲nil obj3?.cls
若引用的實例被釋放後,其在另外一個實例中的引用也將被置爲nil,因此weak只能用於optional類型的屬性,然而在開發中還有一種狀況,某個類必須保有另外一個類的示例,這個實例不能爲nil,可是這個屬性又不能影響其原始實例的釋放,這種狀況也會形成循環引用,示例以下:函數
class MyClassFive{ var cls:MyClassSix init(param:MyClassSix){ cls = param } deinit{ print("ClassFive deinit") } } class MyClassSix{ var cls:MyClassFive? deinit{ print("ClassSix deinit") } } var obj6:MyClassSix? = MyClassSix() var obj5:MyClassFive? = MyClassFive(param: obj6!) obj6?.cls = obj5 obj5=nil obj6=nil //沒有打印任何信息
上面的示例也會形成循環引用,然而MyClassFive類中的cls屬性爲常量不可爲nil,不可以使用weak弱引用來作Swift中又提供了一個關鍵字unowned無主引用來處理這樣的問題,示例以下:學習
class MyClassFive{ unowned var cls:MyClassSix init(param:MyClassSix){ cls = param } deinit{ print("ClassFive deinit") } } class MyClassSix{ var cls:MyClassFive? deinit{ print("ClassSix deinit") } } var obj6:MyClassSix? = MyClassSix() var obj5:MyClassFive? = MyClassFive(param: obj6!) obj6?.cls = obj5 obj5=nil obj6=nil
關於弱引用和無主引用,其區別主要是在於:spa
1.弱引用用於解決Optional值的引發的循環引用。code
2.無主引用用於解決非Optional值引發的循環引用。對象
3.我的覺得,弱引用可用下圖表示:ip
4.無主引用可用以下圖表示:
若將上面的代碼修改以下,程序會直接崩潰:
class MyClassFive{ unowned var cls:MyClassSix init(param:MyClassSix){ cls = param } deinit{ print("ClassFive deinit") } } class MyClassSix{ var cls:MyClassFive? deinit{ print("ClassSix deinit") } } var obj6:MyClassSix? = MyClassSix() var obj5:MyClassFive? = MyClassFive(param: obj6!) obj6?.cls = obj5 obj6=nil obj5?.cls
上面所舉的例子知足了兩種狀況,一種是兩類實例引用的屬性都是Optional值的時候使用weak來解決循環引用,一種是兩類實例有一個爲非Optional值的時候使用unowned來解決循環引用,然而還有第三種狀況,兩類實例引用的屬性都爲非Optional值的時候,可使用無主引用與隱式拆包結合的方式來解決,這也是無主引用最大的應用之處,示例以下:
class MyClassSeven{ unowned var cls:MyClassEight init(param:MyClassEight){ cls = param } deinit{ print("ClassSeven deinit") } } class MyClassEight{ var cls:MyClassSeven! init(){ cls = MyClassSeven(param:self) } deinit{ print("ClassEight deinit") } } var obj7:MyClassEight? = MyClassEight() obj7=nil
除了在兩個類實例間會產生循環引用,在閉包中,也可能出現循環引用,當某個類中包含一個閉包屬性,同時這個閉包屬性中又使用了類實例,則會產生循環引用,示例以下:
class MyClassNine { var name:String = "HS" lazy var closure:()->Void = { //閉包中使用引用值會使引用+1 print(self.name) } deinit{ print("ClassNine deinit") } } var obj9:MyClassNine? = MyClassNine() obj9?.closure() obj9=nil //不會打印析構信息
Swift中提供了閉包的捕獲列表來對引用類型進行弱引用或者無主引用的轉換:
class MyClassNine { var name:String = "HS" lazy var closure:()->Void = { [unowned self]()->Void in print(self.name) } deinit{ print("ClassNine deinit") } } var obj9:MyClassNine? = MyClassNine() obj9?.closure() obj9=nil
捕獲列表以中括號標識,多個捕獲參數則使用逗號分隔。
專一技術,熱愛生活,交流技術,也作朋友。
——琿少 QQ羣:203317592