Swift从入门到精通第二十一篇 - 自动引用计数(ARC) 初识

自动引用计数(ARC)(学习笔记)

环境Xcode 11.0 beta4 swift 5.1

  • 前言
    • SWift 用ARC追踪和管理应用程序的内存使用情况,与OC中的ARC非常相似;引用计数只适用于类的实例,枚举和结构体是值类型,非引用类型并且不存在存储和通过引用传递
  • ARC如何工作
    • 当创建一个类的实例时,ARC会分配一块内存存储实例的信息,包括实例类型信息及实例关联的任意存储属性的值
    • 如果实例不再需要,ARC会释放此实例的内存,以作它用;如果已经销毁实例,再访问将会崩溃
    • 为了确保实例在使用时不会销毁,ARC会记录有多少属性、常量、变量正在引用类实例,只要有一个引用在引用实例就不会销毁实例
    • 当把实例赋值给属性、常量、变量,这将会产生一个强引用,直到这个强引用不再持有,才允许销毁
  • ARC使用
    • 示例:类Person 有一个存储属性 name

      class Person {
          let name: String
          init(name: String) {
              self.name = name
              print("\(name) is being initialized")
          }
          deinit {
              print("\(name) is being deinitialized") 
          }
      }
      // 定义有一个 Person? 类型实例
      var reference1: Person
      var reference2: Person
      var reference3: Person
      reference1 = Person(name: "John Appleseed")
      // Prints "John Appleseed is being initialized"
      reference2 = reference1
      reference3 = reference1
      // 3 strong references
      reference1 = nil
      reference2 = nil
      // 1 strong reference remain
      reference3 = nil
      // Prints "John Appleseed is being deinitialized"
  • 类实例之间的强引用循环
    • 循环使用示例

      class Person {
          let name: String
          init(name: String) { self.name = name }
          var apartment: Apartment?
          deinit { print("\(name) is being deinitialized")}
      }
      class Apartment {
          let unit: String
          init(unit: String) { self.unit = unit}
          var tenant: Persong?
          deinit { print("Apartment \(unit) is being deinitialized") }
      }
      // 定义两个可行变量
      var john: Person?
      var unit4A: Apartment?
      // 创建两个实例
      john = Person(name: "John Appleseed")
      unit4A = Apartment(unit: "4A")
      // 在创建和赋值后之间的引用关系如下图

      引用关系图一

    • 现在可以将两个实例关联起来

      john!.appartment = unit4A
      unit4A!.tenant = john
      // 之间引用关系如下

      引用关系图二

    • 将两个变量置 nil

      john = nil
      unit4A = nil
      // 二者的引用关系图如下,此时两个实例仍然有强引用在,因此不会被销毁

      引用关系图三

  • 类实例间强引用循环的解决
    • Swift 提供两种解决方法: weak引用 和 unowned引用,这两个引用不会让实例间产生强引用
    • weak 主要适用一个实例生命周期较短的那个,弱引用通常会在运行的时候赋值为nil,因此通常将其声明为可选类型的变量而非常量
    • unowned 一个实例有同样的生命周期或者更长的生命周期
    • 当给一个 weak 引用置 nil 时,不会触发 属性观察器
    • 将上面的示例用 weak 改造如下

      class Person {
          let name: String
          init(name: String) { self.name = name }
          var apartment: Apartment?
          deinit { print("\(name) is being deinitialized") }
      }
      class Apartment {
          let unit: String
          init(unit: String) { self.unit = unit }
          // 改造处
          weak var tenant: Person?
          deinit { print("Apartment \(unit) is being deinitialized") }
      }
      var john: Person?
      var unit4A: Apartment?
      //
      john = Person(name: "John Appleseed")
      unit4A = Apartment(unit: "4A")
      // 
      john!.apartment = unit4A
      unit4A!.tenant = john
      // 之间关系引用图如下

      引用关系图四

      john = nil
      // Prints "John Appleseed is being deinitialized"

      引用关系图五

      unit4A = nil
      // Prints "Apartment 4A is being deinitialized"

      引用关系图六

    • unowned 引用与 weak 相似都不产生强引用,不同的是 unowned 引用总是有一个值,ARC不会自动将其置 nil,这意味着定义时要使用非可选类型
    • 只有在确认引用一起在引用实例没有被释放,才能使用 unowned 引用
    • 如果试图在 unowned 实例被释放后访问,将会产生运行时错误
    • 以下是 Customer CreditCard 示例,Customer可以没有 CreditCard ,但 CreditCard 一定属于一个 Customer

      class Customer {
          let name: String
          var card: CreditCard?
          init(name: String) {
              self.name = name
          }
          deinit { print("\(name) is being deinitialized") }
      }
      //
      class CreditCard {
          let number: UInt64
          unowned let customer: Customer
          init(number: UInt64, customer: Customer) {
              self.number = number
              self.customer = customer
          }
          deinit { print("Card #\(number) is being deinitialized") }
      }
      var john: Customer?
      john = Customer(name: "John Appleseed")
      john!.card = CreditCard(number: 1234_5678_9012_3456, customer: john!)
      // 引用关系如下

      引用关系图七

      john = nil
      // 引用关系如下

      引用关系图八

      * 上面的 unowned 示例是安全的引用,如果你要禁止运行时安全检查(例如出于性能考虑)可以用 unowned(unsafe) ,那这样开发者有责任检查代码的安全性;
      如果要访问已经销毁的 unsafe unowned 引用时,将会访问之前的内存位置,是不安全的操作

    • unowned 引用与隐式解包属性
    • 上面的两种解决方法基本包含大部分的情况,但还有第三种情况,就是属性都不能为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)
        }
    }
    //
    class City {
        let name: String
        unowned let country: Country
        init(name: String, country: Country) {
            self.name = name
            self.country = country
        }
    }
    var country = Country(name: "Canada", capitalName: "Ottawa")
    print("\(country.name)'s capital city is called \(country.capitalCity.name)")
    // Prints "Canada's capital city is called Ottawa"
    // 这里有几点说明
    // 1. City 的初始化是在 Country 初始化里,按照前面所说的两阶段初始化是不行,因为 City 初始化里用到 Country,此处的解决办法是隐式解包
    // 这意味着属性 capitalCity 有默认初始值 nil, 只是访问时不需要解包
    // 2. 因此 Country 的初始化在 name 赋值完成时就初始化完毕,后面才可以将 self 作为参数传入
  • 闭包的强引用
    • 示例

      class HTMLElement {
          let name: String
          let text: String?
          lazy var asHTML: () -> String = {
              if let text = self.text {
                  return "<\(self.name)>\(text)</\(self.name)>"
              } else {
                  return "<\(self.name) />"
              }
          }
          init(name: String, text: String? = nil) {
              self.name = name
              self.text = text
          }
          deinit {
              print("\(name) is being deinitialized")
          }
      }
      let heading = HTMLElement(name: "h1")
      let defaultText = "some default text"
      heading.asHTML = {
          return "<\(heading.name)>\(heading.text ?? defaultText)</\(heading.name)>"
      }
      print(heading.asHTML())
      // Prints "<h1>some default text</h1>"
      var paragraph: HTMLElement? = HTMLElement(name: "p", text: "hello, world")
      print(paragraph!.asHTML())
      // Prints "<p>hello, world</p>"
      // 创建一个新实例,引用 示意图如下
      paragraph = nil 
      // 此时实例并不会被销毁

引用关系图九

  • 解决闭包的强引用
    • 定义一个捕获列表,写在参数列表和返回值之前

      lazy var someClosure = {
          [unowned self, weak delegate = self.delegate]
          (index: Int, stringToProcess: String) -> String in
          // closure body goes here
      }
      // 如果没有参数列表,没有返回值
      lazy var someClosure = {
          [unowned self, weak delegate = self.delegate] in
          // closure body goes here
      }
    • unowned weak 引用,两者的区别如上面所说的一样

      class HTMLElement {
          let name: String
          let text: String?
          lazy var asHTML: () -> String = {
              [unowned self] in
              if let text = self.text {
                  return "<\(self.name)>\(text)</\(self.name)>"
              } else {
                  return "<\(self.name) />"
              }
          }
          init(name: String, text: String? = nil) {
              self.name = name
              self.text = text
          }
          deinit {
              print("\(name) is being deinitialized")
          }
      }
      var paragraph: HTMLElement? = HTMLElement(name: "p", text: "hello, world")
      print(paragraph!.asHTML())
      // Prints "<p>hello, world</p>"
      // 创建一个新实例,引用 示意图如下
      paragraph = nil
      // Prints "p is being deinitialized"

引用关系图十

猜你喜欢

转载自www.cnblogs.com/tzsh1007/p/11648678.html