about 1 year ago

Apple 在 iOS 5 之後開放 ARC,開發者不需要再自行 release or retain 來操作記憶體,
swift 的 ARC 與 Objc 並沒有差異太大,開發者需要注意的是 code 之間的記憶體管理,
避免造成 memory leak 等等問題。

ARC 只對 reference type 有用,與 structure 或是 enumeration 等 value type 並無相關。

ARC 範例

class MyClass {
    deinit {
    print("deinit")
  }
}
var c: MyClass? = MyClass()
var c1 = c
var c2 = c
c = nil
c1 = nil
c2 = nil

Strong reference cycle

當指定 instance 給予變數或常數時,稱為 strong reference,
每當有一個 strong reference 指向 instance 時,此 instance 的 reference 就會加一,
當 instance 的 reference 歸零時,此 instance 的記憶體就會被系統回收。

在以下的情況,會讓 instance 的 reference 沒有歸零卻也沒有變數或常數 reference,
導致無法被記憶體回收而造成 memory leak。

class MyClass {
    var c: MyClass2?
  deinit {
    print("myClass deinit")
  }
}
class MyClass2 {
    var c: MyClass?
  deinit {
    print("myClass2 deinit")
  }
}
var c: MyClass? = MyClass()
var c2: MyClass2? = MyClass2()
c!.c = c2
c2!.c = c
c = nil
c2 = nil

在上述的兩個 class 內各自擁有對方的變數,初始化出兩個 instance 後,互相指定給對方的 property,
這時候執行 c = nil 時,因為 c2 的 property 還有 strong reference 到 c,因此 c 不會被回收,
執行 c2 = nil 時,因為沒有被回收到 c 的 property 還有 strong reference 到 c2,
因此 c2 也不會被回收,最後的情況變成這兩個 instance 都不會被回收卻也無法再被使用到。

c (strong)-> MyClass instance (strong)-> c2
c2 (strong)-> MyClass2 instance (strong)-> c

MyClass instance (strong)-> c2
c2 (strong)-> MyClass2 instance (strong)-> c

MyClass instance (strong)-> c2
MyClass2 instance (strong)-> c

要解決 strong reference cycle 的方法,可以使用 weak reference 或是 unowned reference,
若使用這兩個 reference 指向 instance 時,此 instance 的 reference 並不會加一。

class MyClass {
    var c: MyClass2?
  deinit {
    print("myClass deinit")
  }
}
class MyClass2 {
    weak var c: MyClass?
  deinit {
    print("MyClass2 deinit")
  }
}
var c: MyClass? = MyClass()
var c2: MyClass2? = MyClass2()
c!.c = c2
c2!.c = c
c = nil
c2 = nil

重新來看,初始化出兩個 instance 後,互相指定給對方的 property,
這時候執行 c = nil 時,因為 c2 的 property 是 weak reference 到 c,
因此 c 此時的 reference 已經歸零並且被回收,
執行 c2 = nil 時,因為 c 已經被回收,因此 c2 的 reference 也會歸零並且被回收。

c (strong)-> MyClass instance (strong)-> c2
c2 (strong)-> MyClass2 instance (weak)-> c

c2 (strong)-> MyClass2

Unowned reference 跟 weak reference 用法相同,但是 unowned reference 必須要有值,
因此不可以使用 optional,所以會用在非常確定不會被回收到的 instance 上。

class MyClass {
    var c: MyClass2?
  deinit {
    print("myClass deinit")
  }
}

class MyClass2 {
    unowned let c: MyClass
  init(para:MyClass) {
    c = para
  }
  deinit {
    print("myClass2 deinit")
  }
}

var c: MyClass? = MyClass()
c!.c = MyClass2(para:c!)
c = nil

在 closures 內也會發生 strong reference cycle,如果 class 內有 closure,
並且在 body 內去呼叫了 self,當 instance 執行時,便會對 instance 產生 strong reference。

class MyClass {
    let s = "123"
    lazy var property: () -> String = {
    return self.s
  }
}
var c: MyClass? = MyClass()
let s = c.property
c = nil

Swift 提供在 closures 內加入 unowned reference 與 weak reference。

class MyClass {
    let s = "123"
    lazy var property: () -> String = { [unowned self = self]
    return self.s
  }
}
var c: MyClass? = MyClass()
let s = c.property
c = nil
← iOS Swift : Initialization & Deinitialization iOS Swift : Optional Chaining →