Как напечатать тип NotificationCenter.Notifications?

Вопрос или проблема

Я хочу создать асинхронную последовательность, которая будет вызывать событие всякий раз, когда я получаю уведомление для какой-либо группы уведомлений (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() и эффективно обрабатывать уведомления в асинхронной последовательности. Это не только укрощает код, но и делает его более безопасным для использования в многопоточном окружении. Используйте данный подход для интеграции уведомлений в ваши асинхронные конструкции, сохраняя при этом читаемость и поддержку кода.

Оцените материал
Добавить комментарий

Капча загружается...