Xcode13对Swift对象生命周期的优化

·  阅读 3810
Xcode13对Swift对象生命周期的优化

「这是我参与11月更文挑战的第2天,活动详情查看:2021最后一次更文挑战

Xcode13中,在 Build Setting中,新增了 Optimize Object Lifetimes编译选项,默认是关闭的,苹果建议我们将该选项设置为YES,打开此优化项,可以减小Swift对象的生命周期,这样就可以更高效的使用内存。

截屏2021-11-03 下午11.23.16.png

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对象的生命周期从 初始化开始,到最后一次使用结束。

截屏2021-11-04 上午12.06.14.png

在开启优化的情况下,我们运行该函数,结果为

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()
}
复制代码

TravelAccount对象强引用AccountTravel对象弱引用

我们注意到,由于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引用,使用中间类打破对象之间的互相引用。重新设计过后,类声明如下

截屏2021-11-04 下午10.29.56.png

它们之间的引用关系如图所示

截屏2021-11-04 下午10.31.28.png

deinit

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

总结

本文讲述了 Xcode 13Swift对象生命周期的优化,以及开启这项优化之后,可能会带来的问题,并根据问题本身提出了相对应的建议。

本文参考:

WWDC 21: Swift ARC in Swift: Basics and beyond

如果觉得有收获请按如下方式给个 爱心三连:👍:点个赞鼓励一下。🌟:收藏文章,方便回看哦!。💬:评论交流,互相进步!

分类:
iOS
分类:
iOS