13. Swift 循环强引用.png

Swift 使用自动引用计数(ARC)这一机制来跟踪和管理应用程序的内存。通常情况下我们不需要去手动释放内存,因为 ARC 会在类的实例不再被使用时,自动释放其占用的内存。但是,如果强引用还在,实例是不允许被销毁的。

类实例之间的循环强引用

两个类实例互相保持对方的强引用,并让对方不被销毁,这就是所谓的循环强引用。

  1. class Person {
  2. let name: String
  3. var apartment: Apartment?
  4. init(name: String) { self.name = name }
  5. deinit { print("Person \(name) 被析构") }
  6. }
  7. class Apartment {
  8. let number: Int
  9. var tenant: Person? // 房客
  10. init(number: Int) { self.number = number }
  11. deinit { print("Apartment \(number) 被析构") }
  12. }
  13. var person: Person? = Person(name: "huangjian")
  14. var apartment: Apartment? = Apartment(number: 73)
  15. person?.apartment = apartment
  16. apartment?.tenant = person
  17. /**
  18. 当你把这两个变量设为 nil 时,没有任何一个析构函数被调用
  19. 强引用循环阻止了 Person 和 Apartment 类实例的销毁,并在你的应用程序中造成了内存泄漏
  20. */
  21. person = nil
  22. apartment = nil

image.png

解决实例之间的循环强引用

Swift 提供了两种办法用来解决你在使用类的属性时所遇到的循环强引用问题:弱引用和无主引用。

弱引用无主引用允许循环引用中的一个实例引用另外一个实例而不保持强引用,这样实例能够互相引用而不产生循环强引用。对于生命周期中会变为 nil 的实例使用弱引用,而相反的,对于初始化赋值后再也不会被赋值为 nil 的实例,使用无主引用

如在上面的例子中,Person 类如果属性 apartment 可能会变为 nil,使用弱引用:

  1. class Person {
  2. weak var apartment: Apartment?
  3. }

如在上面的例子中,Person 类如果属性 apartment 不会变为 nil,使用无主引用:

  1. class Person {
  2. unowned let apartment: Apartment
  3. }

以上两种解决循环强引用的方法,均只是为属性添加特别的修饰关键词,使用方法与一般的别无二致。

闭包引起的循环强引用

循环强引用还会发生在当你将一个闭包赋值给类实例的某个属性,并且这个闭包体中又使用了实例。

这个闭包体中可能访问了实例的某个属性,例如 self.someProperty,或者闭包中调用了实例的某个方法,例如self.someMethod。这两种情况都导致了闭包捕获 self,从而产生了循环强引用。

比如下面的例子中,HTMLElement 类产生了类实例和 asHTML 默认值的闭包之间的循环强引用。

  1. class HTMLElement {
  2. let name: String
  3. let text: String?
  4. lazy var asHTML: () -> String = {
  5. if let text = self.text {
  6. return "<\(self.name)>\(text)</\(self.name)>"
  7. } else {
  8. return "<\(self.name) />"
  9. }
  10. }
  11. init(name: String, text: String?) {
  12. self.name = name
  13. self.text = text
  14. }
  15. deinit {
  16. print("\(name) is being deinitialized")
  17. }
  18. }
  19. var paragraph: HTMLElement? = HTMLElement(name: "p", text: "hello, world")
  20. print(paragraph!.asHTML())

解决闭包引起的循环强引用

当捕获引用可能会是 nil,将闭包内的捕获定义为弱引用
当捕获引用不会为 nil,或者当闭包和捕获的实例总是互相引用且同时销毁时,将闭包内的捕获定义为无主引用

上面的例子适合使用无主引用,在闭包中添加了 [unowned self] in
**

  1. class HTMLElement {
  2. lazy var asHTML: () -> String = { [unowned self] in
  3. if let text = self.text {
  4. return "<\(self.name)>\(text)</\(self.name)>"
  5. } else {
  6. return "<\(self.name) />"
  7. }
  8. }
  9. }

在闭包内使用 weak 和 unowned 引用,应该用 [] 把它们括起来,这样可以在闭包内定义多个捕获值。

  1. // Look at that sweet Array of capture values.
  2. let closure = { [weak self, unowned otherInstance] in
  3. self?.doSomething() // weak variables are Optionals!
  4. otherInstance.doSomething() // unowned variables are not.
  5. }

备注:[Swift 使用自动引用计数]