Вопрос или проблема
Я хочу создать асинхронную последовательность, которая будет вызывать событие всякий раз, когда я получаю уведомление для какой-либо группы уведомлений (Xcode 16.1 RC). Я подумал, что самый простой способ сделать это — использовать метод merge()
из пакета swift-async-algorithms
от Apple. Моя первоначальная наивная попытка:
let note1 = NotificationCenter.default.notifications(named: .noteOne)
let note2 = NotificationCenter.default.notifications(named: .noteTwo)
let merged = merge(note1, note2) // ❗️ Соответствие 'Notification' 'Sendable' недоступно
Ошибка имеет смысл; я знаю, что Notification
не может быть Sendable
из-за его свойства userInfo
. Поэтому я подумал, что могу просто сопоставить это с чем-то другим, используя
let note1 = NotificationCenter.default.notifications(named: .noteOne)
.map { _ in () }
let note2 = NotificationCenter.default.notifications(named: .noteTwo)
.map { _ in () }
let merged = merge(note1, note2) // ❗️ Соответствие 'Notification' 'Sendable' недоступно
Это не работает; я получаю ту же ошибку компиляции. Так что… как мне это сделать?
Добавление .map { _ in () }
не работает, потому что AsyncMapSequence<Base, Transformed>
соответствует Sendable
только в том случае, если выполнены все эти условия:
Base
являетсяSendable
Base.Element
являетсяSendable
Transformed
являетсяSendable
Смотрите также исходный код.
В этом случае второе условие не выполнено. Notification
не соответствует Sendable
.
Из исходного кода видно, что это соответствие Sendable
на самом деле @unchecked
. Это связано с тем, что AsyncMapSequence
предназначена для использования несоответствующего Sendable
замыкания (оно хранится в хранении), и компилятор не может определить, что это безопасно.
На самом деле, если бы замыкание было @Sendable
, то потребовалось бы только первое условие. Поскольку ваше замыкание просто сопоставляется с ()
, оно является Sendable
, поэтому вы можете написать свою собственную AsyncMapSequence
, которая принимает замыкание @Sendable
.
Поскольку ваше замыкание также не является async
, я написал этот SyncMapSequence
, чтобы упростить задачу,
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)
}
}
Использование:
let note1 = NotificationCenter.default.notifications(named: .noteOne)
.syncMap { _ in () }
let note2 = NotificationCenter.default.notifications(named: .noteTwo)
.syncMap { _ in () }
let merged = merge(note1, note2)
.
Ответ или решение
Как типизировать NotificationCenter.Notifications
в Swift
При работе с асинхронными последовательностями в Swift, особенно с уведомлениями от NotificationCenter
, может возникнуть необходимость объединить события из различных источников. В данном контексте, вы стремитесь использовать метод merge()
из пакета swift-async-algorithms
для объединения уведомлений. Однако, тип уведомления Notification
не соответствует протоколу Sendable
из-за присутствия свойства userInfo
. Это приводит к ошибке компиляции, которую вы уже заметили.
Понимание проблемы
Ошибка, возникающая при попытке вызвать merge()
с уведомлениями, основана на том, что тип Notification
не является Sendable
. К сожалению, даже если вы пытаетесь использовать .map { _ in () }
для преобразования уведомлений в более простой тип, эта операция не решает проблему, так как условия для AsyncMapSequence
все еще не выполняются.
Для решения этой проблемы, вам действительно понадобится создать свою собственную асинхронную последовательность, которая будет обрабатывать вашу логику и ограничения.
Реализация SyncMapSequence
Мы можем создать собственную структуру SyncMapSequence
, которая будет принимать @Sendable
замыкания и позволит вам маппить элементы исходной последовательности без потери совместимости с Sendable
. Ниже приведен код реализации этой структуры:
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)
}
}
Применение
После имплементации SyncMapSequence
, вы можете использовать его для вашего исходного кода, следующим образом:
let note1 = NotificationCenter.default.notifications(named: .noteOne).syncMap { _ in () }
let note2 = NotificationCenter.default.notifications(named: .noteTwo).syncMap { _ in () }
let merged = merge(note1, note2)
Теперь, когда вы применили syncMap()
, вы сможете избежать проблем с компиляцией, так как уведомления теперь преобразовываются в тип, который соответствует требованиям Sendable
.
Заключение
Решение данной проблемы заключается в создании структуры, которая может адаптировать вашу существующую логику для работы с Sendable
. Реализованный подход позволяет вам продолжать использовать merge()
и эффективно обрабатывать уведомления в асинхронной последовательности. Это не только укрощает код, но и делает его более безопасным для использования в многопоточном окружении. Используйте данный подход для интеграции уведомлений в ваши асинхронные конструкции, сохраняя при этом читаемость и поддержку кода.