代码示例:
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]
)。
with*Continuation
用于在异步上下文中包装使用完成块等的遗留代码。您首选的做法是根本不需要使用它,并让整个函数在调用异步上下文中运行(然后它还应该支持级联取消,而不必单独管理任务)。
如果重复调用
someAsyncFunc
,您还面临着无法恢复继续的风险,这比直接使用异步函数更重要。
如果在这之后你仍然有充分的理由继续使用 Continuation,你可以将 self 的释放视为相当于取消,并抛出
CancellationError
,或者你可以使用值 Void
来恢复 Continuation (()
),因为这是整个函数的返回类型。
两个观察:
我们一般使用
[weak self]
来避免强引用循环。这里没有持久的强引用循环,所以不需要[weak self]
。
有时,我们在那些没有持久强引用循环,而只是异步任务运行时的临时强引用循环的情况下使用
[weak self]
模式。
但即使在这种情况下,也不建议使用
[weak self]
。如今,我们不再担心self
的生命周期,而是将注意力转移到异步任务的生命周期上。具体来说,我们确保一旦不再需要异步工作的结果就取消异步工作。这使得 [weak self]
模式变得毫无意义。
在 GCD 时代,取消是很笨拙的。但 Swift 并发提供了一流的取消支持,因此我们应该接受这一点,在视图关闭时取消任务(例如,在
viewDidDisappear
中的 UIKit/AppKit 中,在 .onDisappear
中的 SwiftUI 中)。
说了这么多,假设您真的想做一些像您的问题中所考虑的事情。 (这是不必要的,也是一个糟糕的设计,但让我们将其作为思考练习来讨论。)
考虑您的代码片段:
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 并发中有点反模式。