swiftlang / swift

The Swift Programming Language
https://swift.org
Apache License 2.0
67.28k stars 10.33k forks source link

non-sendable warning on AsyncSequence notification watching #62474

Open drekka opened 1 year ago

drekka commented 1 year ago

Description

I'm getting a warning on this code:

Task {
    for await _ in NotificationCenter.default.notifications(named: await UIApplication.didEnterBackgroundNotification) {
        // DO stuff
    }
}
<unknown>:0: warning: non-sendable type 'Notification?' returned by call from actor-isolated context to non-isolated instance method 'next()' cannot cross actor boundary
Swift.Optional:1:21: note: generic enum 'Optional' does not conform to the 'Sendable' protocol
@frozen public enum Optional<Wrapped> : ExpressibleByNilLiteral {

However it's not indicating any particular line and is not clear as to how I can fix this.

Steps to reproduce

Cut-n-paste the above code into a project.

Expected behavior

Code should compile without warnings.

Environment

bdkjones commented 1 year ago

Following because I'm encountering the same warning and I don't know how to resolve it.

imthath-m commented 1 year ago

I'm facing the same issue as well. Strangely, the warning is showing up in multiple files throughout the project even though I have used this only in one file.

imthath-m commented 1 year ago

Here's a workaround if anyone is interested. The issue seems to be coming from passing the type Notification to/from an async context.


extension NotificationCenter {
  func notificationInfo(named name: Notification.Name) -> AsyncStream<NotificationInfo> {
    AsyncStream { continuation in
      let task = Task {
        for await notification in NotificationCenter.default.notifications(named: name) {
          continuation.yield(NotificationInfo(notification))
        }
      }
      continuation.onTermination = { _ in task.cancel() }
    }
  }
}

final public class NotificationInfo {
  private let notification: Notification

  init(_ notification: Notification) {
    self.notification = notification
  }

  public var name: Notification.Name { notification.name }

  public var object: Any? { notification.object }

  public var userInfo: [AnyHashable: Any]? { notification.userInfo }
}

I just converted Notification to another type (which I initially confirmed to Sendable but later figured out that it works even if remove the Sendable confirmation) before crossing the actor boundary (whatever that means). So instead of getting NotficationCenter.Notifcations which confirms to AsyncSequence protocol, now I have an AsyncStream<NotificationInfo> which also confirms to the same protocol. Hopefully, there will be minimal changes required in the call site once the issue is fixed and I can remove this workaround.

squm commented 1 year ago

warning goes away if the code is inside ‘actor’ method, I can't explain why actor eskimo { func notify() { Task { … } } }

also this stackoverflow answer says this is XCode bug https://stackoverflow.com/questions/71481775/capture-of-self-in-a-sendable-closure

and here they decided to use Combine instead https://developer.apple.com/forums/thread/718565 NotificationCenter.default.publisher …

malhal commented 11 months ago

This explains the problem and suggests to use a detached task:

https://stackoverflow.com/a/77254444/259521

alex-vasenin commented 5 months ago

Actually the documentation on NotificationCenter.notifications(named:object:) already has an example of the solution (for macOS).

Here is the version adapted to iOS:

extension NotificationCenter {
    func notifications(_ name: Notification.Name) -> AsyncMapSequence<NotificationCenter.Notifications, Notification.Name> {
        notifications(named: name).map { $0.name }
    }
}