In your original version, with two [weak self] captures, the following happens when the closure is called:
-
If
selfisnilat this point:- A
Taskis created, weakly capturing self. - The
Taskis immediately executed - The task immediately returns through the
guard, asselfisnil.
- A
-
If
selfis not nil at this point:- A
Taskis created, weakly capturing self. - The
Taskis immediately executed. - The task immediately captures
selfstrongly through theguard. self.nonisolatedMethod()is called (which would also captureselfstrongly!).- The task finishes and
selfis released.
- A
You probably want to remove the inner [weak self] in, particularly if the first thing the Task is doing is guard let self else { return }:
func acceptsClosure(_ x: @escaping () -> Void) {}
final class A: @unchecked Sendable {
func nonisolatedMethod() {}
func createsNestedClosure() {
acceptsClosure { [weak self] in
guard let self else { return }
Task { @MainActor in
nonisolatedMethod()
}
}
}
}
The stored closure will have a weak reference to self until it's executed. Then, one of two things happen:
-
If
selfisnilat this point:- The closure
returns through theguardwithout creating aTask.
- The closure
-
If
selfis not nil at this point:- A
Taskis created, strongly capturing self. - The task is immediately executed.
self.nonisolatedMethod()is called.- The task finishes and
selfis released.
- A
This is usually what you want, because a Task should eventually finish, so you're not creating a retain cycle by having a Task strongly capture self[1]. And in this case, the Task was already strongly capturing self immediately either way.
This thread touches on this topic too, it's worth a read.
Key exception: if the
Taskcan run forever. For example, by containing afor awaitloop over a never-endingAsyncSequence. ↩︎