Nested weak capture and implicit self in Swift 6

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.


  1. Key exception: if the Task can run forever. For example, by containing a for await loop over a never-ending AsyncSequence. ↩︎