如何从两个发布者A和B创建一个Swift Combine发布者,其中发布者B消耗发布者A的价值?

时间:2020-04-22 08:43:31

标签: swift combine userdefaults grdb grdbcombine

我想创建一个实现以下目的的Swift Combine发布者:

  • 发布者应通过 Defaults(一个UserDefaults Swift包) GRDB sqlite数据库值的更改来触发(使用GRDBCombine
  • Defaults发布者处收到的更新后的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 :(得分:0)

假设您希望每次Defaults发布者发出更改时都启动一个新的数据库发布者,则需要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失败类型,因为与所有I / O外部性一样,数据库不是100%可靠的。在Stack Overflow答案中,很难建议此时隐藏错误(例如,将错误变成空的Task数组),因为隐藏错误会阻止您的应用知道错误所在并做出相应的反应。

这里是一个低于版本的版本,可捕获数据库错误。这是我要使用的版本,假设该应用程序仅在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()
}