2

我想创建一个 Swift Combine 发布者,它可以实现以下目标:

  • 发布者应该由默认值(一个Swift 包)的更改或GRDB sqlite 数据库值的更改(使用GRDBCombine)触发。UserDefaults
  • 从DefaultsUserDefaults发布者收到的更新应该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>. 但是,我找不到解决此问题的方法。

4

1 回答 1

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失败类型,因为数据库不是 100% 可靠的,因为所有 I/O 外部性。在 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()
}
于 2020-04-23T09:53:08.073 回答