Swift中如何解決循環引用問題?

 

和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 selfin // 定義捕獲列表,將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,這種狀況適合一個類使用無主屬性,另外一個類使用隱式解析可選類型 .

 

文章來源:簡書

相關文章
相關標籤/搜索