Non-sendable Warning for NotificationCenter.default.notifications(named: ...) in for await

The following code inside an newly created iOS App project results in the following warning: Non-sendable type 'Notification?' returned by call from main actor-isolated context to non-isolated instance method 'next()' cannot cross actor boundary

import SwiftUI

struct ContentView: View {
    var body: some View {
        VStack {
            Image(systemName: "globe")
                .imageScale(.large)
                .foregroundColor(.accentColor)
            Text("Hello, world!")
        }
        .padding()
        .task{
            for await _ in NotificationCenter.default.notifications(named: UIDevice.orientationDidChangeNotification){
                print("orientation changed")
            }
        }
    }
}

What is the correct way to handle this or is it an API error?

Isn't NotificationCenter.default.notifications(named: ...) exactly meant to be used with for ... await aka. next()?

Answered by DTS Engineer in 741003022

Many Foundation types that are ‘obviously’ sendable can’t be marked as sendable. There are two commons reasons for this:

  • Subclassing

  • User info dictionaries

For example, Notification can’t be sendable because it has a user info dictionary and that might contain a non-sendable value.

The best way to handle that varies based on the context. In the specific example that you posted at the top of this thread, you can change the code to look like this:

for await _ in NotificationCenter.default.notifications(named: UIDevice.orientationDidChangeNotification).map( { $0.name } ) {
    print("orientation changed")
}

This maps the async sequence of notifications to an async sequence of notification names, and those are sendable.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

filed feedback with Apple: FB11722934

I'm getting this too. No idea how to solve it.

I've been getting the same thing since somewhere during the Xcode 14 / iOS 16 beta cycle. Xcode will put up a variable, exaggerated number of warnings, none of which seem to actually be properly associated with the code in question, but I do use for await over NotificationCenter's AsyncSequence and assume the warnings are just buggy. Thanks for posting this, as it has been driving me nuts not to be able to find other instances online. Will watch this thread (and update if I find a solution).

Unfortunately in the meantime FB 11722934 was closed by Apple:

"Investigation completed - functional capability according to current design given" (translated from the german status message on the Feedback web page)

In the best interpretation this means the problem is known to behave this way until Swift 6 compatible frameworks come out?

But this is not really a helpful answer.

I even tried to ask about this problem in an Ask Apple: Swift session but it was not covered in the topics answered.

Accepted Answer

Many Foundation types that are ‘obviously’ sendable can’t be marked as sendable. There are two commons reasons for this:

  • Subclassing

  • User info dictionaries

For example, Notification can’t be sendable because it has a user info dictionary and that might contain a non-sendable value.

The best way to handle that varies based on the context. In the specific example that you posted at the top of this thread, you can change the code to look like this:

for await _ in NotificationCenter.default.notifications(named: UIDevice.orientationDidChangeNotification).map( { $0.name } ) {
    print("orientation changed")
}

This maps the async sequence of notifications to an async sequence of notification names, and those are sendable.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

But why isn't Notification.Name Sendable?

    public struct Name : Hashable, Equatable, RawRepresentable, @unchecked Sendable {

        public init(_ rawValue: String)

        public init(rawValue: String)
    }

But why isn't Notification.Name Sendable?

It is. This code compiles just fine:

let n = Notification.Name("abc")
let s: Sendable = n

Note Not that you’d use Sendable in this way normally. As a marker protocol it’s mean to be used as a generic constraint rather than as an existential. However, the code above does do the job of proving that Notification.Name is sendable. If you replace n with some other value — indeed, a Notification value — it fails to compile.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Thanks @eskimo. I think I was confused why this code required await when turning on the Swift 6 extra concurrency warning/errors setting.

        self.applicationDidBecomeActiveSubscriber = NotificationCenter.default.publisher(for: UIApplication.didBecomeActiveNotification).sink() { [weak self] _ in
            self?.applicationDidBecomeActive()
        }

In particular, it's surprising that UIApplication.didBecomeActiveNotification required @MainActor since this is essentially a constant string. It seems that because UIApplication requires @MainActor, referencing these class level properties bears the same obligation.

In my particular case, this forces this code block into a Task{} because I don't want the enclosing function to be async. Which then means that it skips a run-loop execution cycle, causing me not to actually receive the didBecomeActive notification. (Note, this is just one example. I'm using several such notifications for a few different reasons).

The mitigation appears to be to create my own clones of the UIApplication.didDoX Notification.Name constants just to get around this - which of course seems pretty undesirable.

In my particular case, this forces this code block into a Task{} because I don't want the enclosing function to be async.

Can’t you just marked the enclosing function as bound to the main actor?

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Non-sendable Warning for NotificationCenter.default.notifications(named: ...) in for await
 
 
Q