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

举报
Serendipity·y 发表于 2022/02/16 23:48:45 2022/02/16
【摘要】 在 Xcode13 中,在 Build Setting 中,新增 Optimize Object Lifetimes 编译选项,默认是关闭的,Apple 建议将该选项设置为 YES,打开此优化项,可以减小...
  • 在 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")
}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 编译器编译器会在引用开始时插入 retain 操作,以及在最后一次使用时,插入 release 操作,由此可以分析出:
    • ① Travler 对象初始化时,引用计数为 1;
    • ② 在 travl2 引用 trvel1 时,对 Travler 对象进行 retain 操作,此时,引用计数为 2;
    • ③ 在最后一次使用 travel1 时,对 Travler 对象进行 release 操作,此时引用计数为 1;
    • ④ 在最后一次使用 travel2 时,对 Travler 对象进行 release 操作,此时引用计数为 0。
  • 那么,此时 Travler 对象的生命周期从初始化(①)开始,到最后一次使用(④)结束。
  • 在开启优化的情况下,运行该函数,结果为:
Traveler deinit ........
Done traveling

  
 
  • 1
  • 2
  • 可以看出,在执行 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()
}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 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()
    })
}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 或者使用 defer,延长至整个函数结束:
func test() {
    let travel = Traveler(name: "Kody")
    let account = Account(points: 1000, traveler: travel)
    defer {withExtendedLifetime(travel, {
    })}
    travel.account = account
    account.printSummary()
}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 但这种方式不好,这会增加维护成本,也违背减少对象生命周期的初衷。
  • 如果可以把向对象的访问限制为只允许强引用,就可以防止对象生命周期意外。在这里,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()
}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 那么,现在重新设计,避免 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
}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 它们之间的引用关系如图所示:

在这里插入图片描述

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

文章来源: blog.csdn.net,作者:Serendipity·y,版权归原作者所有,如需转载,请联系作者。

原文链接:blog.csdn.net/Forever_wj/article/details/121340881

【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。