在Xcode13中,在 Build Setting中,新增了 Optimize Object Lifetimes编译选项,默认是关闭的,苹果建议我们将该选项设置为YES,打开此优化项,可以减小Swift对象的生命周期,这样就可以更高效的使用内存。
Swift ARC。
在修改编译器设置为YES之前,我们先了解下Swift中的ARC。有以下几点:
- 对象的生命周期从
init()开始到最后一次使用结束。 - 在生命周期结束之后,
ARC会将对象dealloc。 ARC通过引用计数来追踪对象的生命周期。Swift汇编器通过插入retain/release操作,来控制引用计数。- 当对象的
引用计数为0时,Swift runtime会将对象dealloc。
和OC的不同
我们来看下如下代码
class Traveler{
var name: String
var destination: String?
init(name:String) {
self.name = name
}
}
func test(){
let travel1 = Traveler(name: "LiLy") // 1
// Retain
let travel2 = travel1 // 2
//Release // 3
travek2.destination = "Big Sur" // 4
//Release
print("Done traveling")
}
编译器编译器会在引用开始时插入retain操作,以及在最后一次使用时,插入release操作。由此我们可以分析出:
- 1,
Travler对象初始化时,引用计数为1。 - 2,在
travl2引用trvel1时,对Travler对象进行retain操作,此时,引用计数为2。 - 3,在
最后一次使用 travel1时,对Travler对象进行release操作。此时,引用计数为1。 - 4,在
最后一次使用 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: "LiLy")
let account = Account(points: 1000, traveler: travel)
travel.account = account // 2
account.printSummary()
}
Travel对Account对象强引用,Account对Travel对象弱引用。
我们注意到,由于account引用travel是弱引用, 在// 2 代码时,此时,travel对象已经被释放,当执行// 2函数时,travel对象为nil,条件不成立,不会打印旅行者的分数,此时,将会产生一个无声的bug。
withExtendedLifetime
通过 withExtendedLifetime可以延长对象的生命周期,防止潜在的错误。
func test(){
let travel = Traveler(name: "LiLy")
let account = Account(points: 1000, traveler: travel)
travel.account = account
withExtendedLifetime(travel, {
account.printSummary()
})
}
- 将
travel的生命周期延长至account.printSummary()执行完。
或者使用 defer,延长至整个函数结束。
func test(){
let travel = Traveler(name: "LiLy") // 1
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: "LiLy")
let account = Account(points: 1000, traveler: travel)
travel.account = account
travel.printSummary() // 2
}
重新设计避免weak/unown引用
增加一个中间类,把必须的信息存储到中间类中,打破原来的弱引用或者unown引用,使用中间类打破对象之间的互相引用。重新设计过后,类声明如下
它们之间的引用关系如图所示
deinit
开启优化之后,会缩短对象的生命周期,如果在工程在对象的deinit方法做了依赖了外部对象,可能此时依赖的外部对象已经释放,而造成一些逻辑错误。这是一个需要注意的地方。
总结
本文讲述了 Xcode 13对Swift对象生命周期的优化,以及开启这项优化之后,可能会带来的问题,并根据问题本身提出了相对应的建议。
本文参考:
WWDC 21: Swift ARC in Swift: Basics and beyond
如果觉得有收获请按如下方式给个
爱心三连:👍:点个赞鼓励一下。🌟:收藏文章,方便回看哦!。💬:评论交流,互相进步!。