Как я могу создать издателя Swift Combine из двух издателей A и B, где издатель B использует значение издателя A? - PullRequest
2 голосов
/ 22 апреля 2020

Я хочу создать издателя Swift Combine, который достигает следующего:

  • Издатель должен быть вызван изменениями в либо По умолчанию (a UserDefaults пакет Swift) или изменения значений базы данных GRDB sqlite (с использованием GRDBCombine ).
  • Обновленный UserDefaults, полученный из По умолчанию издатель должен использоваться в запросе базы данных в GRDBCombine издателе.

Вот упрощенная версия того, что я пробовал до сих пор:

func tasksPublisher() -> AnyPublisher<[Task], Never> {
    Defaults.publisher(.myUserDefault)
        .flatMap { change in
            let myUserDefault = change.newValue

            return ValueObservation
                .tracking { db in
                    try Task.
                        .someFilter(myUserDefault)
                        .fetchAll(db)
                }
                .removeDuplicates()
                .publisher(in: database)
                .eraseToAnyPublisher()
        }
        .eraseToAnyPublisher()
}

Однако этот издатель выдает следующую ошибку (отредактированную в соответствии с упрощенной версией моего издателя выше):

Cannot convert return expression of type 'AnyPublisher<Publishers.FlatMap<_, AnyPublisher<Defaults.KeyChange<Int>, Never>>.Output, Publishers.FlatMap<_, AnyPublisher<Defaults.KeyChange<Int>, Never>>.Failure>' (aka 'AnyPublisher<_.Output, Never>') to return type 'AnyPublisher<[Task], Never>'

Моя ставка Проблема в том, что два издателя имеют разные значения: [Task] и Defaults.KeyChange<Int>. Однако я не могу найти способ обойти это.

1 Ответ

0 голосов
/ 23 апреля 2020

Если вы хотите запускать нового издателя базы данных каждый раз, когда издатель по умолчанию выдает изменение, вам нужен оператор switchToLatest () .

Для этого оператора нужны ошибки обоих издателей. гармонизированы. Здесь, поскольку Defaults.publisher имеет тип ошибки Never, мы можем использовать оператор setFailureType (to:) , чтобы объединить тип ошибки издателя базы данных: Error.

Это дает:

func tasksPublisher() -> AnyPublisher<[Task], Error> {
    Defaults
        .publisher(.myUserDefault)
        .setFailureType(to: Error.self)
        .map({ change -> DatabasePublishers.Value<[Task]> in
            let myUserDefault = change.newValue
            return ValueObservation
                .tracking { db in
                    try Task
                        .someFilter(myUserDefault)
                        .fetchAll(db)
                }
                .removeDuplicates()
                .publisher(in: database)
        })
        .switchToLatest()
        .eraseToAnyPublisher()
}

Обратите внимание, что возвращаемый издатель имеет тип ошибки Error, поскольку база данных не на 100% надежна, как и все внешние эффекты ввода / вывода. В ответе «Переполнение стека» трудно порекомендовать скрыть ошибки на этом этапе (например, превратив их в пустой массив задач), поскольку сокрытие ошибок не позволяет вашему приложению узнать, что не так, и реагировать соответствующим образом.

Ниже приведена версия, которая содержит ошибки базы данных. Я бы использовал эту версию, предполагая, что приложение просто не может работать, когда SQLite не работает: иногда бесполезно делать вид, что такие низкоуровневые ошибки могут быть перехвачены и обработаны удобным для пользователя способом.

// Traps on database error
func tasksPublisher() -> AnyPublisher<[Task], Never> {
    Defaults
        .publisher(.myUserDefault)
        .map({ change -> AnyPublisher<[Task], Never> in
            let myUserDefault = change.newValue
            return ValueObservation
                .tracking { db in
                    try Task
                        .someFilter(myUserDefault)
                        .fetchAll(db)
                }
                .removeDuplicates()
                .publisher(in: database)
                .assertNoFailure("Unexpected database failure")
                .eraseToAnyPublisher()
        })
        .switchToLatest()
        .eraseToAnyPublisher()
}
...