The following code compiles fine in -swift-version 5, but errors with implicit use of 'self' in closure; use 'self.' to make capture semantics explicit in -swift-version 6
func acceptsClosure(_ x: @escaping () -> Void) {}
@MainActor
final class A {
func nonisolatedMethod() {}
func createsNestedClosure() {
acceptsClosure { [weak self] in
Task { @MainActor [weak self] in
guard let self else { return }
nonisolatedMethod()
// ^ implicit use of 'self' in closure; use 'self.' to make capture semantics explicit
}
}
}
}
If I remove either the outer [weak self] it accepts the code, but I am unsure of the reference capture semantics here (am I good? Is the outer capture not going to retain self?)
SE-0365 offers an official solution, but it seems nonsensical to have to bother with reweakifying. It all apparently went live with Swift 5.8, so hopefully it's a regression and what you posted is actually intended to work as written.
func createsNestedClosure() {
acceptsClosure { [weak self] in
guard let self else { return }
Task { @MainActor [weak self] in
guard let self else { return }
nonisolatedMethod()
}
}
}
In your original version, with two [weak self] captures, the following happens when the closure is called:
If self is nil at this point:
A Task is created, weakly capturing self.
The Task is immediately executed
The task immediately returns through the guard, as self is nil.
If self is not nil at this point:
A Task is created, weakly capturing self.
The Task is immediately executed.
The task immediately captures self strongly through the guard.
self.nonisolatedMethod() is called (which would also capture self strongly!).
The task finishes and self is released.
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 self is nil at this point:
The closure returns through the guard without creating a Task.
If self is not nil at this point:
A Task is created, strongly capturing self.
The task is immediately executed.
self.nonisolatedMethod() is called.
The task finishes and self is released.
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 Task can run forever. For example, by containing a for await loop over a never-ending AsyncSequence. ↩︎