I want to create an asynchronous sequence that emits whenever I receive a notification for some group of notifications (Xcode 16.1 RC). My thought was that the easiest way to do this is to use the merge()
method from Apple's swift-async-algorithms
package. My initial naive attempt:
let note1 = NotificationCenter.default.notifications(named: .noteOne)
let note2 = NotificationCenter.default.notifications(named: .noteTwo)
let merged = merge(note1, note2) // ❗️ Conformance of 'Notification' to 'Sendable' is unavailable
The error makes sense; I know that Notification
can't be Sendable
because of its userInfo
property. So I thought I could just map that to something else using
let note1 = NotificationCenter.default.notifications(named: .noteOne)
.map { _ in () }
let note2 = NotificationCenter.default.notifications(named: .noteTwo)
.map { _ in () }
let merged = merge(note1, note2) // ❗️ Conformance of 'Notification' to 'Sendable' is unavailable
This doesn't work; I get the same compilation error. So... how do I do this?
Adding .map { _ in () }
doesn't work because AsyncMapSequence<Base, Transformed>
only conforms to Sendable
if all of these conditions are met:
Base
is Sendable
Base.Element
is Sendable
Transformed
is Sendable
See also the source code.
In this case, the second condition is not met. Notification
does not conform to Sendable
.
From the source code, this Sendable
conformance is actually @unchecked
. This is because AsyncMapSequence
is designed to take non-Sendable
closure (it stores this in a stored property), and the compiler can't tell that this is safe.
In fact, if the closure had been a @Sendable
one, then only the first condition is required. Since your closure just maps to ()
, it is Sendable
, so you can write your own AsyncMapSequence
that takes a @Sendable
closure instead.
Since your closure is also not async
, I have written this SyncMapSequence
to make things simple,
public struct SyncMapSequence<Base, Transformed>: AsyncSequence where Base: AsyncSequence {
let f: @Sendable (Base.Element) -> Transformed
let base: Base
public struct AsyncIterator: AsyncIteratorProtocol {
var baseIterator: Base.AsyncIterator
let f: @Sendable (Base.Element) -> Transformed
public mutating func next() async throws -> Transformed? {
try await baseIterator.next().map(f)
}
}
public func makeAsyncIterator() -> AsyncIterator {
AsyncIterator(baseIterator: base.makeAsyncIterator(), f: f)
}
}
extension SyncMapSequence: Sendable where Base: Sendable {}
extension AsyncSequence {
func syncMap<Transformed>(transform: @Sendable @escaping (Element) -> Transformed) -> SyncMapSequence<Self, Transformed> {
SyncMapSequence(f: transform, base: self)
}
}
Usage:
let note1 = NotificationCenter.default.notifications(named: .noteOne)
.syncMap { _ in () }
let note2 = NotificationCenter.default.notifications(named: .noteTwo)
.syncMap { _ in () }
let merged = merge(note1, note2)