Swift高级进阶

文章40 |   阅读 18241 |   点赞0

来源:https://blog.csdn.net/forever_wj/category_6167373.html

Swift之深入解析Xcode13对Swift对象生命周期的优化

x33g5p2x  于2022-03-08 转载在 其他  
字(3.1k)|赞(0)|评价(0)|浏览(397)
  • 在 Xcode13 中,在 Build Setting 中,新增 Optimize Object Lifetimes 编译选项,默认是关闭的,Apple 建议将该选项设置为 YES,打开此优化项,可以减小 Swift 对象的生命周期,这样就可以更高效的使用内存。

  • 在修改编译器设置为 YES 之前,先了解下 Swift 中的 ARC,需要注意以下几点:
    • 对象的生命周期从 init() 开始到最后一次使用结束;
    • 在生命周期结束之后,ARC 会将对象 dealloc;
    • ARC 通过引用计数来追踪对象的生命周期;
    • Swift 汇编器通过插入 retain/release 操作,来控制引用计数;
    • 当对象的引用计数为 0 时,Swift runtime 会将对象 dealloc。
  • 现有如下示例代码:
class Traveler {
    var name: String
    var destination: String?
    init(name:String) {
        self.name = name
    }
}
func test() {
    let travel1 = Traveler(name: "Kody")   // ①
    // Retain
    let travel2 = travel1                  // ②
    // Release                             // ③
    travek2.destination = "Big Sur"        // ④    
    // Release
    print("Done traveling")
}
  • 编译器编译器会在引用开始时插入 retain 操作,以及在最后一次使用时,插入 release 操作,由此可以分析出:
    • ① Travler 对象初始化时,引用计数为 1;
    • ② 在 travl2 引用 trvel1 时,对 Travler 对象进行 retain 操作,此时,引用计数为 2;
    • ③ 在最后一次使用 travel1 时,对 Travler 对象进行 release 操作,此时引用计数为 1;
    • ④ 在最后一次使用 travel2 时,对 Travler 对象进行 release 操作,此时引用计数为 0。
  • 那么,此时 Travler 对象的生命周期从初始化(①)开始,到最后一次使用(④)结束。
  • 在开启优化的情况下,运行该函数,结果为:
Traveler deinit ........
Done traveling
  • 可以看出,在执行 print(“Done traveling”) 之前,Traveler 已经被释放,这样能够保证对象的最短生命周期。和 C++ 、OC 是不一样的,后者是在右括号执行完成后,才会销毁对象。
  • 在大多数情况下,是没有问题的,但是如果存在弱引用(weak)或无主引用(unown)就需要特别注意,来看如下示例:
class Traveler {
    var name: String
    var account: Account?
    init(name:String) {
        self.name = name
    }
}

class Account {
    weak var traveler: Traveler?
    var points: Int

    init(points: Int, traveler: Traveler?) {
        self.traveler = traveler
        self.points = points
    }
    
    func printSummary() {
      if let travel = traveler {
            print("\(travel.name) has \(points) points")
        }
    }
}

func test() {    
    let travel = Traveler(name: "Kody")  
    let account = Account(points: 1000, traveler: travel)
    travel.account = account
    account.printSummary()
}
  • Travel 对 Account 对象强引用,Account 对 Travel 对象弱引用。可以注意到,由于 account 引用 travel 是弱引用, 在代码 travel.account = account 时,此时,travel 对象已经被释放,当执行 travel.account = account 时,travel 对象为 nil,条件不成立,不会打印分数,此时,将会产生 bug。
  • 通过 withExtendedLifetime 可以延长对象的生命周期,防止潜在的错误。如下所示,将 travel 的生命周期延长至 account.printSummary() 执行完:
func test() {
    let travel = Traveler(name: "Kody")
    let account = Account(points: 1000, traveler: travel)
    travel.account = account
    withExtendedLifetime(travel, {
        account.printSummary()
    })
}
  • 或者使用 defer,延长至整个函数结束:
func test() {
    let travel = Traveler(name: "Kody")
    let account = Account(points: 1000, traveler: travel)
    defer {withExtendedLifetime(travel, {
    })}
    travel.account = account
    account.printSummary()
}
  • 但这种方式不好,这会增加维护成本,也违背减少对象生命周期的初衷。
  • 如果可以把向对象的访问限制为只允许强引用,就可以防止对象生命周期意外。在这里,printSummary() 函数被移回到了 Traveler 类并且 Account 类中的弱引用是隐藏的,现在必须通过 Travel 调用 printSummary() 函数,由于在 Traveler 中,对 Account 是强引用,可以消除潜在的错误。如下所示:
class Traveler {
    var name: String
    var account: Account?
    init(name:String) {
        self.name = name
    }
    
   func printSummary() {
      if let account = account {
            print("\(name) has \( account.points) points")
        }
    }
}

class Account {
    private weak var traveler: Traveler?
    var points: Int

    init(points: Int, traveler: Traveler?) {
        self.traveler = traveler
        self.points = points
    }
}
func test() {
    let travel = Traveler(name: "Kody")  
    let account = Account(points: 1000, traveler: travel)
    travel.account = account
    travel.printSummary()
}
  • 那么,现在重新设计,避免 weak/unown 引用。增加一个中间类,把必须的信息存储到中间类中,打破原来的弱引用或者 unown 引用,使用中间类打破对象之间的互相引用。重新设计过后,类声明如下:
class PersonalInfo {
	var name: String
}

class T raveler {
	var info: PersonalInfo
	var account: Account?
}

class Account {
	var info: PersonalInfo
	var points: Int
}
  • 它们之间的引用关系如图所示:

  • 开启优化之后,会缩短对象的生命周期,如果在工程在对象的 deinit 方法做了依赖外部对象,那么可能此时依赖的外部对象已经释放,从而造成一些逻辑错误,这里需要注意。

相关文章