Mat*_*ney 33 concurrency ios async-await swift swift-concurrency
好的,我们都知道,在 Swift 的传统并发中,如果您在类中执行(例如)网络请求,并且在完成该请求时您引用了属于该类的函数,则必须传入[weak self],例如这:
func performRequest() {
apiClient.performRequest { [weak self] result in
self?.handleResult(result)
}
}
Run Code Online (Sandbox Code Playgroud)
这是为了阻止我们self在闭包中强烈捕获并导致不必要的保留/无意中引用已经从内存中删除的其他实体。
在异步/等待中怎么样?我在网上看到了相互矛盾的事情,所以我只想向社区发布两个示例,看看您对这两个示例有何看法:
class AsyncClass {
func function1() async {
let result = await performNetworkRequestAsync()
self.printSomething()
}
func function2() {
Task { [weak self] in
let result = await performNetworkRequestAsync()
self?.printSomething()
}
}
func function3() {
apiClient.performRequest { [weak self] result in
self?.printSomething()
}
}
func printSomething() {
print("Something")
}
}
Run Code Online (Sandbox Code Playgroud)
function3很简单 - 老式并发意味着使用[weak self].
function2我认为是对的,因为我们仍在闭包中捕获内容,所以我们应该使用[weak self].
function1这是由 Swift 处理的,还是我应该在这里做一些特别的事情?
Rob*_*Rob 61
[weak self]最重要的是,将捕获列表与对象一起使用通常没有什么意义Task。请改用取消模式。
一些详细的考虑:
\n不需要弱捕获列表。
\n你说:
\n\n\n在 Swift 的传统并发中,如果您在类内执行(例如)网络请求,并且在完成该请求时引用属于该类的函数,则必须传递
\n[weak self]\xe2\x80\xa6
这不是真的。是的,使用捕获列表可能是谨慎或明智的做法[weak self],但这不是必需的。weak\xe2\x80\x9cm 必须\xe2\x80\x9d 使用引用的唯一时间self是当存在持久的强引用循环时。
对于编写良好的异步模式(被调用的例程在完成后立即释放闭包),不存在持续的强引用循环风险。这[weak self]不是必需的。
尽管如此,弱捕获列表还是有用的。
\n在这些传统的转义关闭模式中使用[weak self]仍然有用。weak具体来说,在没有对 的引用的情况下self,闭包将保持对 的强引用,self直到异步过程完成。
一个常见的示例是当您发起网络请求以显示场景中的某些信息时。如果在某些异步网络请求正在进行时关闭场景,则将视图控制器保留在内存中,等待仅更新早已消失的关联视图的网络请求是没有意义的。
\n不用说,weak引用self实际上只是解决方案的一部分。如果没有必要保留等待self异步调用的结果,则通常也没有必要继续异步调用。例如,我们可以将a的weak引用与取消挂起的异步进程的 a 结合起来。selfdeinit
弱捕获列表在 Swift 并发中用处不大。
\n考虑你的这种排列function2:
func function2() {\n Task { [weak self] in\n let result = await apiClient.performNetworkRequestAsync()\n \xe2\x80\xa6\n }\n}\nRun Code Online (Sandbox Code Playgroud)\n这看起来不应该对selfwhile performNetworkRequestAsyncis in Progress 保持强引用。但是对属性 的引用apiClient将引入强引用,而不会出现任何警告或错误消息。例如,在下面,我AsyncClass在红色路标处让其超出了范围,但尽管有[weak self]捕获列表,但直到异步过程完成后它才被释放:
在这种情况下,捕获[weak self]列表的作用很小。请记住,在 Swift 并发中,幕后发生了很多事情(例如, \xe2\x80\x9c 挂起点 \xe2\x80\x9d 之后的代码是 \xe2\x80\x9ccontinuation\xe2\x80\x9d 等。 )。它与简单的 GCD 调度不同。请参阅Swift 并发:幕后花絮。
但是,如果您weak也进行所有属性引用,那么它将按预期工作:
func function2() {\n Task { [weak self] in\n let result = await self?.apiClient.performNetworkRequestAsync()\n \xe2\x80\xa6\n }\n}\nRun Code Online (Sandbox Code Playgroud)\n希望未来的编译器版本能够警告我们这种隐藏的对self.
使任务可取消。
\n与其担心是否应该使用weak对 的引用self,不如考虑简单地支持取消:
var task2: Task<Void, Never>?\n\nfunc function2() {\n task2?.cancel() // you might want to cancel previous task, if any; it depends\xe2\x80\xa6\n\n task2 = Task {\n let result = await apiClient.performNetworkRequestAsync()\n \xe2\x80\xa6\n task2 = nil\n }\n}\nRun Code Online (Sandbox Code Playgroud)\n进而:
\noverride func viewDidDisappear(_ animated: Bool) {\n task2?.cancel()\n \xe2\x80\xa6\n super.viewDidDisappear(animated)\n}\nRun Code Online (Sandbox Code Playgroud)\n显然,这是假设您的任务支持取消。大多数 Apple 异步 API 都是如此。(但是,如果您编写了自己的withUnsafeContinuation- 样式实现,那么您将需要定期检查Task.isCancelled或将您的调用包装在withTaskCancellationHandler或其他类似的机制中以添加取消支持。但这超出了本问题的范围。)
现在,详细信息将根据您的具体要求而有所不同。例如,上面的模式仅跟踪Task每个函数的一个。如果您可能多次调用它,那么您将需要弄清楚如何处理它。您是否希望始终取消前一项任务(如上所示),以便始终只有一项任务需要跟踪?或者您想要跟踪多个任务,以便在视图关闭时可以取消所有任务。它有所不同。而且,SwiftUI 通常甚至需要完全不同的模式(例如,如果在.task\xc2\xa0{\xe2\x80\xa6}视图修饰符中启动任务,那么当视图关闭时它会自动取消)。这完全取决于您的具体要求。
但不要迷失在这些细节中:总体思路是在不再需要任务时取消任务。这就消除了对任务保持强引用时间self超过所需时间的担忧。
Ita*_*ber 16
\n\n如果您正在类内执行(例如)网络请求,并且在完成该请求时您引用了属于该类的函数,则必须传递 [weak self],如下所示
\n
这不完全正确。当您在 Swift 中创建闭包时,默认情况下会保留闭包引用或“关闭”的变量,以确保在调用闭包时这些对象可以有效使用。这包括self, whenself在闭包内部被引用。
您想要避免的典型保留周期需要满足两件事:
\nself, 和self保留封盖如果强烈保留闭包,则会发生保留循环self,并且默认情况下,闭包self强烈保留 \xe2\x80\x94 ARC 规则,无需进一步干预,两个对象都不能被释放(因为有东西保留了它),因此内存永远不会被释放。
有两种方法可以打破这个循环:
\nself显式地断开闭包与调用完闭包之间的链接,例如 ifself.action是一个引用 的闭包self,一旦被调用就赋值nil给,例如self.action
self.action = { /* Strongly retaining `self`! */\n self.doSomething()\n\n // Explicitly break up the cycle.\n self.action = nil\n}\nRun Code Online (Sandbox Code Playgroud)\n这通常不适用,因为它是一次性的self.action,并且您还有一个保留周期,直到您调用self.action(). 或者,
让其中一个对象不保留另一个对象。通常,这是通过确定父子关系中哪个对象是另一个对象的所有者来完成的,并且通常最终会强保留闭包,而通过弱引用self闭包,以避免保留它selfweak self
这些规则都是正确的,无论闭包是什么self,也不管它做什么:是否是网络调用、动画回调等。
apiClient使用原始代码,实际上只有 if是 的成员才具有保留周期self,并且在网络请求期间保留闭包:
func performRequest() {\n apiClient.performRequest { [weak self] result in\n self?.handleResult(result)\n }\n}\nRun Code Online (Sandbox Code Playgroud)\n如果闭包实际上被分派到其他地方(例如,apiClient不直接保留闭包),那么您实际上不需要[weak self],因为从一开始就没有循环!
规则与 Swift 并发完全相同Task:
Task来初始化它的闭包默认保留它引用的对象(除非您使用[weak ...])Task在任务期间(即执行时)保持闭包self保留,您将有一个保留周期Task在 的情况下function2(),Task会异步启动和分派,但self 不会保留结果Task对象,这意味着不需要[weak self]。相反,如果function2()存储创建的Task,那么您将有一个潜在的保留周期,您需要将其分解:
class AsyncClass {\n var runningTask: Task?\n\n func function4() {\n // We retain `runningTask` by default.\n runningTask = Task {\n // Oops, the closure retains `self`!\n self.printSomething()\n }\n }\n}\nRun Code Online (Sandbox Code Playgroud)\n如果您需要保留该任务(例如,这样您就可以完成cancel它),您将希望避免让该任务保留self回来(Task { [weak self] ... })。
| 归档时间: |
|
| 查看次数: |
13176 次 |
| 最近记录: |