大多数浏览器和
Developer App 均支持流媒体播放。
-
Swift 中的 ARC:基础功能和拓展功能
了解 Swift 中对象生命周期和 ARC 的基础知识。深入了解哪些语言特性使对象生命周期可观察,依赖于观察到的对象生命周期会带来什么影响,以及一些安全的修复技术。
资源
-
下载
♪低音音乐播放♪ ♪ 梅加娜古普塔:嗨 我叫梅加娜 今天 我将与你谈论Swift中的ARC Swift提供了强大的值类型 如结构体和枚举 你应该尽可能使用值类型 以避免因引用类型而导致的 意外共享的危险 类是Swift中的引用类型 如果你决定使用它们 Swift通过自动引用计数或ARC 来管理其内存 为了编写有效的Swift代码 了解ARC的工作原理很重要 这就是我们在本次座谈中要做的 我将首先回顾Swift与ARC中的 对象生命周期 然后 我将会描述什么是 可观察对象的生命周期 我将详细解释 哪些语言特性能使 对象生命周期可观察 依赖观察到的对象生命周期的后果 以及一些安全的、能修复它们的技术 让我们开始吧 Swift中对象的生命周期始于初始化 而终于最后的使用 ARC会通过在对象生命周期结束后 释放对象来自动管理内存 它通过追踪引用计数 来确定对象的生命周期 ARC主要通过插入retain操作 和release操作的Swift编译器来驱动 在运行时 retain增加引用计数 而release会减少它 当引用计数降为零时 对象将被释放 让我们通过一个例子 来看看它是如何工作的 想象一下 我们想要构建一个 旅行应用程序 为了代表旅行者 让我们编写一个 带有名称 和可选目的地属性的类 在test()函数中 首先创建一个对象Traveler 然后它的引用被复制 最后 它的目的地被更新了 为了自动管理 Traveler对象的内存 Swift编译器在引用开始时 插入retain操作 以及在最后一次使用引用之后 插入release操作 traveler1是Traveler 对象的第一个引用 它的最后一个使用是副本 在这里 Swift编译器 在最后一次使用 traveler1引用后 立即插入一个release操作 它在引用开始时 不插入retain操作 因为初始化将引用计数设置为1 traveler2是对 Traveler对象的另一个引用 它最后一次使用的是目的地更新 在这里 Swift编译器在引用开始时 插入了一个retain操作 和在最后一次使用引用之后 立即插入一个release操作 让我们逐步执行代码 看看在运行时会发生什么 首先 在堆上创建Traveler对象 并使用引用计数1进行初始化 然后在准备新的引用时 执行retain操作 将引用计数增加到2 现在traveler2也是对 Traveler对象的引用 在最后一次使用traveler1引用后 执行release操作 将引用计数减为1 然后Traveler对象的目的地 更新为Big Sur 由于这是Traveler2 引用的最后一次使用 因此执行release操作 将引用计数递减为零 一旦引用计数下降到零 对象就可以被释放 Swift中的对象生命周期 是基于使用的 对象的保证最短生命周期 始于初始化开始 而终于最后一次使用 这与C++等语言不同 在C++中 对象的生命周期会保证 在右大括号时结束 在这个例子中 我们看到对象 在上次使用后立即被释放出来 然而实际上 对象的生命周期 是由Swift编译器插入的 retain和release操作决定的 并且根据启动的ARC优化 观察到的对象生命周期可能会依照 从他们保证的最小值开始 在对象的最后一次使用之后结束 而有所不同 在这种情况 对象会在上次使用后的 程序点被释放 在大多数情况下 对象的确切生命周期 是什么并不重要 但是 由于具有弱引用和无主引用 以及析构器副作用等语言特性 观察对象的生命周期是可能的 如果你的程序 依赖于观察到的对象生命周期 而不是保证对象生命周期 你在未来可能会遇到问题 因为依靠观察到的物体生命周期 在今天可能有效 但这只是巧合 观察到的对象生命周期 是Swift编译器的 一个紧急属性 可以随着 实施细节的变化而改变 此类错误在开发过程中 可能不会被发现 并且可能会隐藏很长时间 只有通过改进的ARC优化 或启用以前有限的ARC优化 不相关的源代码更改的 编译器更新才能发现 我会讲解让对象生命周期 可观察的语言特性 看看如果我们只依赖 观察到的对象生命周期会怎么样 以及一些修复它们的安全技术 与作为强引用的默认引用不同 弱引用和无主引用并不会参与 在引用计数中 因此 它们通常用于中断引用周期 在我深入细节之前 让我们看看什么是引用周期 这是我们旅行应用程序的扩展 我们现在要介绍一个可选的积分系统 旅行者可以拥有一个账户 并在其中累积积分 为了表示这一点 我们有一个带有 积分属性的新账户类别 Account类别指向Traveler类别 而Traveler类别又指回了账户类别 在测试函数中我们创建 Traveler和Account对象 然后通过旅行者引用 调用printSummary()函数 让我们逐步完成代码 看看ARC会发生什么 首先 在堆上创建Traveler对象 引用计数为1 然后在堆上创建Account对象 引用计数为1 由于Account对象引用Traveler对象 因此Traveler对象的 引用计数增加到2 现在Traveler对象 开始引用Account对象 因此Account对象的引用计数 也增加到两个 这是账户引用的最后一次使用 在此之后 账户引用消失 并且Account对象的引用计数 减为1 然后 调用printSummary()函数 来打印名称和点数 这是Traveler引用的最后一次使用 在此之后 Traveler引用消失 Traveler对象的引用计数 减为一 即使所有使对象可达的引用都消失了 对象的引用计数仍然是一 这是因为引用周期 因此 对象永远不会被释放 从而导致内存泄漏 你可以使用弱引用或无主引用 来打破引用循环 因为他们不参与引用计数 在使用弱引用或无主引用时 被引用的对象可能会被释放 发生这种情况时 Swift运行时间 会安全地将弱引用的途径 转为nil 将无主引用的转为trap 任何参与引用循环的引用都可以 被标记为弱引用或无主引用 以打破引用循环 这取决于应用程序 在我们的范例中 让我们将Account类中的 旅行者引用标记为弱引用 因为弱引用不参与 引用计数 在最后一次使用Traveler对象后 其引用计数降至零 一旦Traveler对象的引用计数为零 它就可以被释放 当Traveler对象消失时 它对Account对象的引用也消失了 使其引用计数为零 现在可以取消分配Account对象 在这个例子中 我们使用弱引用 只用来中断引用循环 如果在保证对象生命周期结束时 使用弱引用访问对象 并且你依赖于观察到的 对象的生命周期来让对象可用 你可能会在将来遇到错误 在观察到的对象的生命周期 出于不相干的原因 发生变化的时候 让我们看一个例子 这里 printSummary()函数 从Traveler类移至Account类 test()函数 现在通过账户引用 调用printSummary()函数 在调用printSummary()函数时 到底发生了什么? 它今天可能会 打印旅行者的姓名和积分 但这只是巧合 这是因为Traveler对象的 最后一次使用 是在调用printSummary()函数之前 在此之后 如果编译器 最后一次使用后 立即插入了一个release Traveler对象的引用计数 可能会降为零 如果引用计数降为零 则通过弱引用 访问Traveler对象的途径 将为nil 并且Traveler对象可能会被释放 所以当printSummary()函数被调用时 弱Traveler引用的强制解包会陷入 导致崩溃 你可能想知道 强制解包是否是这里崩溃的原因 并且可选绑定可能阻止了它 可选绑定实际上使问题变得更糟 如果没有明显的崩溃 它会创建一个无声的错误 而当观察到的对象生命周期 发生变化时 出于不相关的原因 可能不会被发现 有不同的技术 可以安全地处理弱引用和无主引用 他们每个人都有 不同程度的前期实施成本 与持续维护成本相比 让我们用我们的例子一一探讨它们 Swift提供了 withExtendedLifetime() 实用程序 可以显式地 延长对象的生命周期 使用withExtendedLifetime() 可以安全地延长 Traveler对象的生命周期 在调用printSummary()函数时 防止潜在的错误 通过在现有范围的末尾对 withExtendedLifetime() 进行空调用 可以实现相同的效果 对于更复杂的情况 我们可以要求 编译器延长对象的生命周期 使用延迟到当前范围的末尾 withExtendedLifetime() 看起来像是摆脱 对象生命周期错误的一种简单方法 然而 这种技术是脆弱的 并将纠正的责任转移到你身上 使用这种方法 你应该确保在每次弱引用 都有可能导致错误时 使用withExtendedLifetime() 如果不加以控制 withExtendedLifetime() 可能会蔓延到整个代码库 增加维护成本 使用更好的API重新设计类 是一种更有原则的方法 如果可以把向对象的访问 限制为只允许强引用 就可以防止对象生命周期意外 在这里 printSummary()函数被移回 到了Traveler类 并且Account类中的弱引用是隐藏的 测试现在被迫通过强引用 调用printSummary()函数 消除潜在的错误 除了带来性能成本之外 弱引用和无主引用可能会暴露错误 如果你在类别设计不小心一点的话 重要的是要停下来思考 为什么需要弱引用和无主引用? 它们仅用于打破引用循环吗? 如果你首先避免 创建引用循环会怎样? 通常可以通过重新思考算法 和把循环类关系转换到树结构 来避免引用循环 在我们的例子中 Traveler类需要指向Account类 Account类实际上没有必要 指向Traveler类 Account类只需要 访问旅行者的个人信息 我们可以把旅客的个人信息 移进一个名为PersonalInfo的新类别 Traveler类和Account类 都可以指向到 PersonalInfo类来避免循环 避免对弱引用和无主引用的需求 可能会产生额外的实现成本 但这是个明确的方法 可以消除所有对象生命周期 潜在的错误 另一个使对象生命周期 变得可观察的语言特性 是析构器副作用 析构器在释放之前运行 可以观察到它的副作用 受外部程序影响 如果你编写代码以使用外部程序效果 对析构器副作用进行排序 它可能导致隐藏的错误 只有当观察到的 对象生命周期发生变化时 由于不相关的原因才会被发现 在我讨论这些错误是如何出现之前 让我们看看什么是析构器 这是第一个例子的重复 现在有一个析构器 析构器有一个全局副作用 在控制台上打印一条消息 今天 析构器可能会在 打印“完成旅行”后运行 但是由于Traveler对象 最后一次使用的是目的地更新 所以析构器可以运行 在打印“完成旅行”之前 取决于启动的ARC优化 在这个例子中 析构器副作用是可以观察到的 但并不依赖于此 让我们看一个更复杂的例子 其中的析构器副作用 被外部程序效果所依赖 我们现在将旅行指标引入 Traveler类别 每当更新目的地时 它记录在TravelMetrics类别中 最终在取消初始化Traveler对象时 指标会发布到全局记录 发布的指标是旅行者的匿名ID 查找的目的地数量 和计算出的旅行兴趣类别 在test()函数中 首先创建一个Traveler对象 然后从Traveler对象复制 对travelMetrics的引用 旅行者的目的地更新为Big Sur 它在TravelMetrics中记录Big Sur 旅行者的目的地更新为Catalina 它在TravelMetrics中记录Catalina 然后通过查看记录的目的地 来计算旅行兴趣类别 今天 析构器可能会在 计算旅行兴趣后运行 将感兴趣的类别发布为Nature 但是Traveler对象的最后一次使用 是将目的地更新到Catalina 马上在析构器可以运行之后 由于析构器在计算 旅行兴趣之前运行 所以nil被发布 会导致错误 就像弱引用和无主引用一样 这里有不同的技术 能安全地处理析构器副作用 他们每个都有不同程度的 前期实施成本 与持续维护成本相比 让我们一一看看它们 withExtendedLifetime()可用于 显式延长Traveler对象的生命周期 直到计算出旅行兴趣类别 从而防止潜在的错误 如前所述 这会将纠正的责任转移到你身上 使用这种方法 你应该确保 withExtendedLifetime 在每次析构器副作用之间 可能存在不正确的交互 和外部程序影响时使用 增加维护成本 如果效果是局部的 则无法观察到 析构器副作用 通过限制内部类别细节的可见性 来重新设计类别API 可以防止对象生命周期错误 在这里 TravelMetrics 被标记为私有 以防止外部访问 析构器现在计算 最感兴趣的旅行类别 并发布指标 这是有效的 但更有原则的方法是 完全摆脱析构器副作用 这里使用defer而不是析构器 来发布指标 并且析构器只执行验证 通过消除析构器副作用 我们可以消除所有 潜在的对象生命周期错误 我们探索了我们的 教育旅行应用程序范例 来了解ARC 弱引用和无主引用以及析构器副作用 彻底理解使对象生命周期 可观察的语言特性很重要 并消除对观察到的对象生命周期的 潜在错误依赖 这样我们就不会 在意外的时候发现错误 在Xcode 13上 有一个新实验性构建设置 名为“优化对象生命周期” 可用于Swift编译器 这使ARC优化的生命周期大大缩短 启用此构建设置后 你可能会看到对象在上次使用后 立即被更加一致地释放 使观察到的对象生命周期 更接近他们的最低保证 这可能会暴露隐藏的 对象生命周期错误 类似于所讨论的范例 你可以按照本次座谈中讨论的 安全技术来消除所有此类别的错误 我希望你喜欢本次座谈 谢谢观看 ♪
-
-
1:49 - ARC Example
class Traveler { var name: String var destination: String? } func test() { let traveler1 = Traveler(name: "Lily") let traveler2 = traveler1 traveler2.destination = "Big Sur" print("Done traveling") }
-
6:37 - Reference Cycle Example
class Traveler { var name: String var account: Account? func printSummary() { if let account = account { print("\(name) has \(account.points) points") } } } class Account { var traveler: Traveler var points: Int } func test() { let traveler = Traveler(name: "Lily") let account = Account(traveler: traveler, points: 1000) traveler.account = account traveler.printSummary() }
-
9:05 - Weak Reference Example
class Traveler { var name: String var account: Account? func printSummary() { if let account = account { print("\(name) has \(account.points) points") } } } class Account { weak var traveler: Traveler? var points: Int } func test() { let traveler = Traveler(name: "Lily") let account = Account(traveler: traveler, points: 1000) traveler.account = account traveler.printSummary() }
-
10:05 - Accessing an object via weak reference
class Traveler { var name: String var account: Account? } class Account { weak var traveler: Traveler? var points: Int func printSummary() { print("\(traveler!.name) has \(points) points") } } func test() { let traveler = Traveler(name: "Lily") let account = Account(traveler: traveler, points: 1000) traveler.account = account account.printSummary() }
-
11:14 - Accessing an object via optional binding of weak reference
class Traveler { var name: String var account: Account? } class Account { weak var traveler: Traveler? var points: Int func printSummary() { if let traveler = traveler { print("\(traveler.name) has \(points) points") } } } func test() { let traveler = Traveler(name: "Lily") let account = Account(traveler: traveler, points: 1000) traveler.account = account account.printSummary() }
-
11:45 - Safe techniques for handling weak references - withExtendedLifetime()
func test() { let traveler = Traveler(name: "Lily") let account = Account(traveler: traveler, points: 1000) traveler.account = account withExtendedLifetime(traveler) { account.printSummary() } } func test() { let traveler = Traveler(name: "Lily") let account = Account(traveler: traveler, points: 1000) traveler.account = account account.printSummary() withExtendedLifetime(traveler) {} } func test() { let traveler = Traveler(name: "Lily") let account = Account(traveler: traveler, points: 1000) defer {withExtendedLifetime(traveler) {}} traveler.account = account account.printSummary() }
-
12:55 - Safe techniques for handling weak references - Redesign to access via strong reference
class Traveler { var name: String var account: Account? func printSummary() { if let account = account { print("\(name) has \(account.points) points") } } } class Account { private weak var traveler: Traveler? var points: Int } func test() { let traveler = Traveler(name: "Lily") let account = Account(traveler: traveler, points: 1000) traveler.account = account traveler.printSummary() }
-
14:20 - Safe techniques for handling weak references - Redesign to avoid weak/unowned reference
class PersonalInfo { var name: String } class Traveler { var info: PersonalInfo var account: Account? } class Account { var info: PersonalInfo var points: Int }
-
15:23 - Deinitializer Example
class Traveler { var name: String var destination: String? deinit { print("\(name) is deinitializing") } } func test() { let traveler1 = Traveler(name: "Lily") let traveler2 = traveler1 traveler2.destination = "Big Sur" print("Done traveling") }
-
16:10 - Sequencing deinitializer side-effects with external program effects
class Traveler { var name: String var id: UInt var destination: String? var travelMetrics: TravelMetrics // Update destination and record travelMetrics func updateDestination(_ destination: String) { self.destination = destination travelMetrics.destinations.append(self.destination) } // Publish computed metrics deinit { travelMetrics.publish() } } class TravelMetrics { let id: UInt var destinations = [String]() var category: String? // Finds the most interested travel category based on recorded destinations func computeTravelInterest() // Publishes id, destinations.count and travel interest category func publish() } func test() { let traveler = Traveler(name: "Lily", id: 1) let metrics = traveler.travelMetrics ... traveler.updateDestination("Big Sur") ... traveler.updateDestination("Catalina") metrics.computeTravelInterest() } verifyGlobalTravelMetrics()
-
17:56 - Safe techniques for handing deinitalizer side effects - withExtendedLifetime()
func test() { let traveler = Traveler(name: "Lily", id: 1) let metrics = traveler.travelMetrics ... traveler.updateDestination("Big Sur") ... traveler.updateDestination("Catalina") withExtendedLifetime(traveler) { metrics.computeTravelInterest() } }
-
class Traveler { ... private var travelMetrics: TravelMetrics deinit { travelMetrics.computeTravelInterest() travelMetrics.publish() } } func test() { let traveler = Traveler(name: "Lily", id: 1) ... traveler.updateDestination("Big Sur") ... traveler.updateDestination("Catalina") }
-
class Traveler { ... private var travelMetrics: TravelMetrics func publishAllMetrics() { travelMetrics.computeTravelInterest() travelMetrics.publish() } deinit { assert(travelMetrics.published) } } class TravelMetrics { ... var published: Bool ... } func test() { let traveler = Traveler(name: "Lily", id: 1) defer { traveler.publishAllMetrics() } ... traveler.updateDestination("Big Sur") ... traveler.updateDestination("Catalina") }
-
-
正在查找特定内容?在上方输入一个主题,就能直接跳转到相应的精彩内容。
提交你查询的内容时出现错误。请检查互联网连接,然后再试一次。