和OC同樣,swift開發中也是使用自動引用計數ARC(Auto Reference Counteting)來自動管理內存的,因此咱們不須要過多考慮內存管理.當某個類實例不須要用到的時候,ARC會自動釋放其佔用的內存.swift
ARC僅僅能對類的實例作內存管理,也就是隻能針對引用類型.結構體和枚舉都是值類型,不能經過引用的方式來傳遞和存儲,因此ARC也就不能對它們進行內存管理.api
什麼狀況下會致使循環引用閉包
在swift中,每建立一個實例,ARC都會爲其分配一塊內存空間,而在不使用的時候,ARC會釋放和收回那個實例所佔的內存空間,該實例的屬性和方法也就不可以被訪問,若是要訪問就會致使程序崩潰.函數
怎麼肯定實例不被使用了?ARC會自動追蹤實例被多少常量和變量引用.每追蹤到一個,自動引用計數會加一,減小一個引用自動引用計數會減一,若是當自動引用計數變爲0的時候,ARC就會收回內存,銷燬實例.對象
下面是一個自動引用計數的實例:內存
class Person {element
let name: String開發
init(name: String) {get
self.name = namestring
print("\(name)正在被初始化")
}
deinit {
print("\(name)即將被銷燬") // person3 = nil時打印
}
}var person1: Person? // 可選類型的變量,方便置空var person2: Person?var person3: Person?
person1 = Person(name: "Dariel") //建立Person實例並與person1創建了強引用
person2 = person1 // 只要有一個強引用在,實例就能不被銷燬
person3 = person1 // 目前該實例共有三個強引用
person1 = nil
person2 = nil // 由於還有一個強引用,實例不會被銷燬
person3 = nil // 最後一個強引用被斷開,ARC會銷燬該實例
上面的例子中建立的 Person 實例最後引用計數變爲了0被銷燬了,但現實世界並不會一直都這麼美好, ARC這種機制也有本身的侷限性,請看下面的例子:
class People {
let name: String
init(name: String) { self.name = name }
var apartment: Apartment? // 人住的公寓屬性deinit {
print("People被銷燬")
}
}
class Apartment {
let unit: String
init(unit: String) { self.unit = unit }
var tenant: People? // 公寓中的人的屬性
deinit {
print("Apartment被銷燬")
}
}
var people1: People? = People(name: "Dariel") // 定義兩個實例變量var apartment1: Apartment? = Apartment(unit: "4A")
people1!.apartment = apartment1 // 二者相互引用
apartment1?.tenant = people1 // 並且彼此都是強引用
people1 = nil
apartment1 = nil // 兩個引用都置爲nil了,但實例並無銷燬
這一次直接建立了兩個實例, People 中有一個 Apartment 的屬性, Apartment 中又有一個 People 屬性,當咱們建立了兩個實例後分別給實例中的這兩個屬性賦完值,又將兩個可選變量賦值爲nil,並無看到兩個實例被銷燬的打印信息( deinit 函數會在實例被銷燬的時候打印).
也就是說ARC並無銷燬兩個對象.那麼問題在哪裏?
當兩個可選變量被賦值爲nil時,ARC並無以爲這兩個實例已經不在使用了.由於兩個實例的相互賦值時使得各自的引用計數+1,這也就是發生循環引用了.
怎麼解決循環引用
1. 若是產生循環引用的兩個屬性都容許爲nil,這種狀況適合用弱引用來解決
隨便哪個可選類型的屬性前面均可以加 weak ,但記住只要加一個就好了.
話很少說上代碼:
class OtherPeople {
let name: String
init(name: String) { self.name = name }
var apartment: OtherApartment? // 人住的公寓屬性
deinit { print("\(name)被銷燬") }
}
class OtherApartment {
let unit: String
init(unit: String) { self.unit = unit }
weak var tenant: OtherPeople? // 加一個weak關鍵字,表示該變量爲弱引用
deinit { print("\(unit)被銷燬") }
}
var otherPeople1: OtherPeople? = OtherPeople(name: "Dariel") // 定義兩個實例變量var otherApartment1: OtherApartment? = OtherApartment(unit: "4A")
otherPeople1!.apartment = otherApartment1 // 二者相互引用
otherApartment1?.tenant = otherPeople1 // 但tenant是弱引用
otherPeople1 = nil
otherApartment1 = nil // 實例被銷燬,deinit中都會打印銷燬的信息
在 OtherPeople 和 OtherApartment 兩個類中,相互引用的兩個屬性都爲可選類型,那麼能夠在一個屬性的前面添加 weak 關鍵字,使該變量變爲弱引用.
對的,沒錯,這個weak仍是之前OC裏面的那個weak.
2. 若是產生循環引用的兩個屬性一個容許爲nil,另外一個不容許爲nil,這種狀況適合用無主引用來解決
只能在不能爲nil的那個屬性前面加 unowned 關鍵字,就是說 unowned 設置之後即便它原來引用的內容已經被釋放了,它仍然會保持對被已經釋放了的對象的一個 "無效的" 引用,它不能是 Optional 值,也不會被指向 nil。若是嘗試去調用這個引用的方法或者訪問成員屬性的話,程序就會崩潰.
無主引用的例子:
class Dog {
let name: String
var food: Food?
init(name: String) {
self.name = name
}
deinit { print("\(name)被銷燬") }
}class Food {
let number: Int
unowned var owner: Dog // owner是一個無主引用
init(number: Int, owner: Dog) {
self.number = number
self.owner = owner
}
deinit { print("食物被銷燬") }
}
var dog1: Dog? = Dog(name: "Kate")
dog1?.food = Food(number: 6, owner: dog1!) // dog強引用food,而food對dog是無主引用
dog1 = nil // 這樣就能夠同時銷燬兩個實例了
Dog 的 food 屬性能夠爲空,而 Food 的 owner 屬性不能爲空,咱們把 owner 設爲無主引用.
3. 若是產生循環引用的兩個屬性都必須有值,不能爲nil,這種狀況適合一個類使用無主屬性,另外一個類使用隱式解析可選類型
隱式解析可選類型: 相似可選類型,默認值能夠設置爲nil
兩個屬性一個在類型後面加 ! 設置爲隱式解析可選類型,另外一個在屬性前面加unowned 關鍵字,設置爲無主屬性.
class Country {
let name: String
var capitalCity: City! // 初始化完成後能夠當非可選類型使用
init(name: String, capitalName: String) {
self.name = name
self.capitalCity = City(name: capitalName, country: self)
}
deinit { print("Country實例被銷燬") }
}
class City {
let name: String
unowned let country: Country
init(name: String, country: Country) {
self.name = name
self.country = country
}
deinit { print("City實例被銷燬") }
}
// 這樣一條語句就可以建立兩個實例var country: Country? = Country(name: "China", capitalName: "HangZhou")print(country!.name) // Chinaprint(country!.capitalCity.name) // HangZhou
country = nil // 同時銷燬兩個實例
Country 的 City 屬性後加!爲隱式解析可選屬性,相似可選類型,capitalCity屬性的默認值爲nil,一旦在 Country 的構造函數中給 name 屬性賦完值後, Country 的整個初始化過程就完成了,就能將 self 做爲參數傳遞給 City 的構造函數了.
總而言之,就是一條語句建立兩個實例,還不產生循環引用.
閉包也是引用類型,怎麼解決閉包的循環強引用
閉包中對任何其餘元素的引用都是會被閉包自動持有的。若是咱們在閉包中寫了self 這樣的東西的話,那咱們其實也就在閉包內持有了當前的對象。這裏就出現了一個在實際開發中比較隱蔽的陷阱:若是當前的實例直接或者間接地對這個閉包又有引用的話,就造成了一個 self -> 閉包 -> self 的循環引用
怎樣避免這種狀況呢?
能夠在閉包開始的時候添加一個標註,來表示這個閉包內的某些要素應該以何種特定的方式來使用
看例子:
class Element {
let name: String
let text: String?
lazy var group:() -> String = { // 至關於一個沒有參數返回string的函數
[unowned self] in // 定義捕獲列表,將self變爲無主引用
if let text = self.text { // 解包
return "\(self.name), \(text)"
}else {
return "\(self.name)"
}
}
init(name: String, text: String? = nil) {
self.name = name
self.text = text
}
deinit { print("\(name)被銷燬") }
}
var element1: Element? = Element(name: "Alex", text: "Hello")print(element1!.group()) // Alex, Hello,閉包與實例相互引用
element1 = nil // self爲無主引用,實例能被銷燬
在閉包中定義一個捕獲列表 [unowned self] ,將self變爲無主引用.這樣就可以在避免產生循環強引用了.
小結
解決循環引用的三種方法,這三種方法的產生主要仍是swift中要考慮屬性爲空的狀況.
· 若是產生循環引用的兩個屬性都容許爲nil,這種狀況適合用弱引用來解決.
· 若是產生循環引用的兩個屬性一個容許爲nil,另外一個不容許爲nil,這種狀況適合用無主引用來解決.
· 若是產生循環引用的兩個屬性都必須有值,不能爲nil,這種狀況適合一個類使用無主屬性,另外一個類使用隱式解析可選類型 .
文章來源:簡書