# Classes with Delegates and @Sendable closures in Swift 6

**URL:** <https://forums.swift.org/t/classes-with-delegates-and-sendable-closures-in-swift-6/75217>\
**Category:** Using Swift\
**Tags:** concurrency\
**Created:** [October 7, 2024, 10:59pm UTC](https://forums.swift.org/t/classes-with-delegates-and-sendable-closures-in-swift-6/75217 "2024-10-07T22:59:39Z")\
**Posts on this page:** 4\
**Page:** 1

<div class="post-metadata">

**Author:** ![codingWithTom](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/codingwithtom/32/37325_2.png) [@codingWithTom](https://forums.swift.org/u/codingWithTom)\
**Post date:** [October 7, 2024, 10:59pm UTC](https://forums.swift.org/t/classes-with-delegates-and-sendable-closures-in-swift-6/75217/1 "2024-10-07T22:59:39Z")

</div>

I keep struggling with Swift 6. Currently I don't understand how are we supposed to create a class with a delegate that's able to do async work in @Sendable closures.

Small example:

I have a class `Service` that has a method to trigger its implementation. It also has a `delegate` to notify that the triggered method finished.

```swift
protocol ServiceDelegate: AnyObject {
  func serviceDidSomething(withData: Data)
  func somethingWentWrong()
}

final class Service {
  weak var delegate: ServiceDelegate?
  
  func doSomething() {
    guard let url = URL(string: "www.foo.com") else { return }
    Task {
      do {
        let (data, _) = try await URLSession.shared.data(for: .init(url: url))
        delegate?.serviceDidSomething(withData: data)
      } catch {
        delegate?.somethingWentWrong()
      }
    }
  }
}

```

Building with complete concurrency this throws a warning "Task-isolated value of type '() async -\> ()' passed as a strongly transferred parameter; later accesses could race; this is an error in the Swift 6 language mode".

I don't quite understand how to approach this when it comes to @Sendable closures for several reasons:

- I can make the method async and have it return instead of notifying the delegate but that's avoiding the situation I want to solve. The one triggering the method is not necessarily the one interested in the result.
- I cannot switch this class to an actor or make it conform to Sendable. The `delegate` variable is mutable by anyone referencing this class, which is pretty much what I want and what I believe to be a common pattern.
- What if `doSomething` doesn't have an alternative with `async await` and I have to use a `@Sendable` closure? For example what if I want to use `Timer.scheduledTimer(..., block: @escaping @Sendable (Timer) -> Void`?

I tried looking at something similar from Apple and went with `URLSession` since it also has a delegate variable and does async work.

```swift
open class URLSession : NSObject, @unchecked Sendable {
    ....
    open var delegate: (any URLSessionDelegate)? { get }
}

```

Yes, adding `@unchecked Sendable` does make the warning go away. Is it really the correct approach? I feel like I am cheating here.

---

<div class="post-metadata">

**Author:** ![CrystDragon](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/crystdragon/32/10461_2.png) [@CrystDragon](https://forums.swift.org/u/CrystDragon)\
**Post date:** [October 8, 2024, 5:32am UTC](https://forums.swift.org/t/classes-with-delegates-and-sendable-closures-in-swift-6/75217/2 "2024-10-08T05:32:05Z")

</div>

If you want your `Service` to be able to trigger the methods of `delegate` in any context, than perhaps you should build some synchronization mechanism around the property `delegate`.

The easiest way, just as you've figured out, is to totally bypass the checking by writing `final class Service: @unchecked Sendable`. This is OK for the code above, because loading and storing a `weak` variable is thread safe. However, it leaves a burden to the maintainer of this `Service` class to manually guarantee concurrency safety in the future.

My way is to limit such unsafe escapes to the minimum scope:

```swift
final class Service {

  @propertyWrapper
  class ServiceDelegate: @unchecked Sendable {
    private weak var core: ServiceDelegate?
    var wrappedValue: ServiceDelegate? {
      get {
        core
      }
      set {
        core = newValue
      }
    }
  }

  @ServiceDelegate
  var delegate

  func doSomething() {
    guard let url = URL(string: "www.foo.com") else { return }

    Task { [_delegate] in
      do {
        let (data, _) = try await URLSession.shared.data(for: .init(url: url))
        _delegate.wrappedValue?.serviceDidSomething(withData: data)
      } catch {
        _delegate.wrappedValue?.somethingWentWrong()
      }
    }
  }
}

```

---

<div class="post-metadata">

**Author:** ![vns](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/vns/32/37396_2.png) [@vns](https://forums.swift.org/u/vns)\
**Post date:** [October 8, 2024, 7:57am UTC](https://forums.swift.org/t/classes-with-delegates-and-sendable-closures-in-swift-6/75217/3 "2024-10-08T07:57:15Z")

</div>

Hi! There are issues in your code from the concurrency perspective, and several ways to address this, depending on details.

> [@codingWithTom](#):
>
> I have a class `Service` that has a method to trigger its implementation. It also has a `delegate` to notify that the triggered method finished.

Your class interacts with concurrency and spawns _unstructured_ task, it is unsafe by design to have this, because it can easily race: just call `doSomething` several times and there it is. That's why Swift warns you here. As for the warning itself,

> Task-isolated value of type '() async -\> ()' passed as a strongly transferred parameter; later accesses could race; this is an error in the Swift 6 language mode.

It clearly needs an improvement, I think there are already few issues on the matter, so hopefully with the next updates there will be an improvement.

Now, to the solutions:

> [@codingWithTom](#):
>
> I don't quite understand how to approach this when it comes to @Sendable closures for several reasons:

When it comes to `@Sendable` closures, it simply requires code within the closure to be safe to pass across isolations, because closure itself can be called from anywhere.

> [@codingWithTom](#):
>
> - I can make the method async and have it return instead of notifying the delegate but that's avoiding the situation I want to solve. The one triggering the method is not necessarily the one interested in the result.

That is actually the best way to start: in that way you are not introducing an unstructured task, allowing, for instance, easy to propagate cancellation. This won't solve all the issues still, because then you'll have to call `doSomething` as part of a `Task` later, and `Service` not being isolated or `Sendable` still will give you a warning/error.

And that brings us to the next point:

> [@codingWithTom](#):
>
> - I cannot switch this class to an actor or make it conform to Sendable. The `delegate` variable is mutable by anyone referencing this class, which is pretty much what I want and what I believe to be a common pattern.

You need to take a look from isolation perspective on your code. The most common thing, I guess, is having this part of an UI flow, so you can mark `Service` to be a `@MainActor`-isolated type. In this way it will be safe to call `async` methods on it, and many will be able to set a delegate.

If your case not includes UI here, but you still want to make it safe and potentially accessible from any isolation, you can do one of three more things:

1. Actually, make it an actor. Setting a `delegate` will require an `async` context and a setter method, but you'll be able to mutate by anyone, just do this safely.
2. Make method isolated using `isolated` parameter: `func doSomething(isolation: isolated (any Actor)? = #isolation)`. This allows to inherit whatever isolation it is called from, and rest will do the compiler: it will ensure that `Service` actually called from only one isolation.
3. Make it `Sendable` / `@unchecked Sendable`. In case you want to avoid additional suspension points, but still make it safe in concurrent world, you can ensure that your state mutations are protected on your own. For instance, you can utilize `Mutex` from `Synchronization` module if it is available for you, or just use `NSLock`.

> [@codingWithTom](#):
>
> Yes, adding `@unchecked Sendable` does make the warning go away. Is it really the correct approach? I feel like I am cheating here.

So addressing this point, _ONLY_ adding `@unchecked Sendable` isn't a correct approach, it means you have to ensure safety on your own, not just turn off compiler checks.

* * *

There is a one more approach: require `ServiceDelegate` to be `Sendable` and avoid capturing `self`:

> **Snippet**
>
> ```swift
> protocol ServiceDelegate: AnyObject, Sendable {
> // ...
> }
> 
> final class Service {
> // ...
> 
> func doSomething() {
> // ...
> let delegate = delegate
> Task { /* ... */ }
> }
> }
> 
> ```

Then implementations of `ServiceDelegate` _have to_ satisfy this `Sendable` requirement. **I do not recommend this as go-to strategy** , this can be useful in certain situations, but not as default way to solve the issues, mostly because putting `Sendable` requirement on a delegate implementation part is a huge complication.

* * *

To sum-up, I personally would go with `MainActor` isolation if we assume this is part of a UI app in Apple ecosystem:

```swift
protocol ServiceDelegate: AnyObject {
  func serviceDidSomething(withData: Data)
  func somethingWentWrong()
}

@MainActor
final class Service {
  weak var delegate: ServiceDelegate?
  
  func doSomething() async {
    guard let url = URL(string: "www.foo.com") else { return }
    do {
      let (data, _) = try await URLSession.shared.data(for: .init(url: url))
      delegate?.serviceDidSomething(withData: data)
    } catch {
      delegate?.somethingWentWrong()
    }
  }
}

```

This gives you most of the options in the simplest way.

---

<div class="post-metadata">

**Author:** ![jaleel](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/jaleel/32/45076_2.png) [@jaleel](https://forums.swift.org/u/jaleel)\
**Post date:** [October 8, 2024, 8:31am UTC](https://forums.swift.org/t/classes-with-delegates-and-sendable-closures-in-swift-6/75217/4 "2024-10-08T08:31:20Z")

</div>

There are good responses already, but a bit off topic—imho structrured concurrency works better with functional code, rather than oop patterns. If it's not much to refactor—what you can do instead of delegates is to use closures and wrapping structs, like:

```swift
final class Service: Sendable {
  
  struct Delegate: Sendable {
    let serviceDidSomething: @Sendable (_ withData: Data) -> ()
    let somethingWentWrong: @Sendable (any Error) -> ()
  }
  
  let delegate: Delegate
  
  init(delegate: Delegate) {
    self.delegate = delegate
  }
  
  func doSomething() {
    Task {
      guard let url = URL(string: "www.foo.com") else { return }
      do {
        let (data, _) = try await URLSession.shared.data(for: .init(url: url))
        self.delegate.serviceDidSomething(data)
      } catch {
        self.delegate.somethingWentWrong(error)
      }
    }
  }
}

```

it also gives a bit more structuring, cause usually people do:

```swift
let service = Service()
service.delegate = self

```

and if it's not set directly after calling—it could lead to different problems. So in the end, with closure approach—you're forced to provide implementation on init. Which of course debatable, but again think it's actually a better way to structure code:

```swift
let service = Service(
  delegate: Service.Delegate(
    serviceDidSomething: { [weak self] data in
      // do something
    },
    somethingWentWrong: { [weak self] error in
      // do something
    }
  )
)

```

* * *

Also, this could be an example, but is delegate even needed? Code could be just:

```swift
class Service { // or even better struct?
  
  enum Error: Swift.Error {
    case invalidURL
  }
  
  func doSomething() async throws -> Data {
    guard let url = URL(string: "www.foo.com") else { throw Error.invalidURL }
    return try await URLSession.shared.data(for: .init(url: url)).0
  }
}

```

and whoever calling `doSomething` then should do something with it.
