Swift Concurrency 中的 withCheckedContinuation 和 [weak self]?

问题描述 投票:0回答:2

代码示例:

    func someAsyncFunc() async {
        loadingTask?.cancel()
        return await withCheckedContinuation { [weak self] checkedContinuation in
            guard let self else {
                return
            }
            loadingTask = Task { [weak self] in
                guard let self else {
                    return
                }
                //some code with self
                checkedContinuation.resume()
            }
        }
    }

我知道其中一个

[weak self]
调用是额外的 - 这个示例仅显示了可以放置它的 2 个可能的代码块。

根据一篇文档文章,

withCheckedContinuation
应始终调用
checkedContinuation.resume()
,根据另一篇文档文章,您应该使用
[weak self]
来避免循环引用。但如何同时使用它们呢?

如果我按原样保留代码,则可能不会调用

checkedContinuation.resume()
,并导致内存泄漏。如果我不使用
[weak self]
,它会导致保留循环,从而导致内存泄漏。我也不能只用
withCheckedContinuation
替换
withCheckedThrowingContinuation
,因为它会增加代码复杂性,也意味着我应该忘记
withCheckedContinuation
(因为任何类似的方法都可能包含
[weak self]
)。

swift memory-leaks task retain-cycle swift-concurrency
2个回答
0
投票

with*Continuation
用于在异步上下文中包装使用完成块等的遗留代码。您首选的做法是根本不需要使用它,并让整个函数在调用异步上下文中运行(然后它还应该支持级联取消,而不必单独管理任务)。

如果重复调用

someAsyncFunc
,您还面临着无法恢复继续的风险,这比直接使用异步函数更重要。

如果在这之后你仍然有充分的理由继续使用 Continuation,你可以将 self 的释放视为相当于取消,并抛出

CancellationError
,或者你可以使用值
Void
来恢复 Continuation (
()
),因为这是整个函数的返回类型。


0
投票

两个观察:

  1. 我们一般使用

    [weak self]
    来避免强引用循环。这里没有持久的强引用循环,所以不需要
    [weak self]

  2. 有时,我们在那些没有持久强引用循环,而只是异步任务运行时的临时强引用循环的情况下使用

    [weak self]
    模式。

    但即使在这种情况下,也不建议使用

    [weak self]
    。如今,我们不再担心
    self
    的生命周期,而是将注意力转移到异步任务的生命周期上。具体来说,我们确保一旦不再需要异步工作的结果就取消异步工作。这使得
    [weak self]
    模式变得毫无意义。

    在 GCD 时代,取消是很笨拙的。但 Swift 并发提供了一流的取消支持,因此我们应该接受这一点,在视图关闭时取消任务(例如,在

    viewDidDisappear
    中的 UIKit/AppKit 中,在
    .onDisappear
    中的 SwiftUI 中)。

  3. 说了这么多,假设您真的想做一些像您的问题中所考虑的事情。 (这是不必要的,也是一个糟糕的设计,但让我们将其作为思考练习来讨论。)

    考虑您的代码片段:

    func someAsyncFunc() async {
        loadingTask?.cancel()
        return await withCheckedContinuation { [weak self] checkedContinuation in
            loadingTask = Task { [weak self] in
                guard let self else {
                    return
                }
                //some code with self
                checkedContinuation.resume()
            }
        }
    }
    

    首先有两个问题

    guard let self else {…}

    • 首先,任务通常会立即开始,因此您通常会在

      guard
      被取消初始化之前传递第一个
      self
      语句,因此第一个
      guard
      语句可以完成任何事情。

    • 其次,在第一个

      guard
      语句中,您在没有调用
      resume
      的情况下返回,这是无效的。在
      withCheckedContinuation
      中,您必须
      resume
      一次且仅一次。但你却没有打电话
      resume
      就回来了。如果发生这种情况(鉴于我的第一点,这是极不可能的),检查的延续将报告错误。

但是,再次强调,不要因为担心潜在的强引用循环风险而盲目插入

[weak self]
模式,因为这里没有强引用循环,这在 Swift 并发中有点反模式。

© www.soinside.com 2019 - 2024. All rights reserved.