【问题标题】:Swift Combine, Avoid nested subscribers on optionalsSwift Combine,避免可选选项上的嵌套订阅者
【发布时间】:2020-05-13 21:41:29
【问题描述】:

我有一个包含 User 对象的 AccountService 类。用户对象从网络请求中异步设置。 在我的 UI 中,我正在显示用户并希望使更改保持最新。我正在使用 Swift 组合框架来执行此操作。

问题:有没有办法避免对象上的嵌套订阅者?

我写了一些测试代码来说明这一点:

import UIKit
import Combine

class User: ObservableObject, CustomDebugStringConvertible {
    @Published
    var name: String

    init(name: String) {
        self.name = name
    }

    var debugDescription: String {
        return self.name
    }
}

class AccountService {

    @Published
    var user: User? = nil

    var userCancel: AnyCancellable?
    var userContentCancel: AnyCancellable?
    init() {
        self.userCancel = self.$user.sink { (user) in
            print("Set user: \(String(describing: user))")

            guard let user = user else { return }
            self.userContentCancel = user.$name.sink { _ in
                print("new name: \(String(describing: self.user))")
            }
        }
    }

    func setUseru(user: User) {
        self.user = user
    }

    func changeUserName(name: String) {
        self.user?.name = name
    }
}

let x = AccountService()
x.setUseru(user: User(name: "Philipp"))
x.changeUserName(name: "Tom")
x.setUseru(user: User(name: "Anna"))

在 Playgroud Xcode 版本 11.4.1、Swift 5 中运行

输出

Set user: nil
Set user: Optional(Philipp)
new name: nil
new name: Optional(Philipp)
Set user: Optional(Anna)
new name: Optional(Tom)

理想情况下,我只想在设置对象时收听self.$user.sinkAND,以了解对象内容何时更改。 我在设置用户名时玩过self.objectWillChange.send(),但我无法触发外部发布者。

我正在寻找摆脱的方法

guard let user = user else { return }
self.userContentCancel = user.$name.sink { _ in
    print("new name: \(String(describing: self.user))")
}

在我的实现中,只需从同一个 self.$user.sink { (user) in 实现中驱动所有内容。

【问题讨论】:

  • 如果可能的话,你的用户应该是一个结构体,因此是不可变的。然后您将只拥有外部订阅者,因为更改名称需要User 的新实例。如果需要,请使用相同的底层唯一标识符确认Identifiable,以便您知道这是具有不同名称的同一用户。您不能从User 中触发AccountService 发布者,因为用户对象不知道它被帐户服务引用
  • @Paulw11 这是个好主意。我会到处玩。问题是可以通过监视用户对象的更改侦听器来更新用户对象。这是在用户内部实现的,因此它不会污染其余代码。所以 changeUserName() 在更新时从内部调用。
  • 如果你采用不变性,那么changeUserName() 就会消失。你可以在你的结构 user(withNewName:String) 上有一个函数,它返回一个新的用户实例,它的其他属性相同,但名称是新的。很难确切地说出你应该做什么,因为我不知道你想要达到什么目标。您可以使用combineLatest 将两个发布者合并为一个。
  • 将它们组合成一个似乎是个好主意。我也可以很好地隐藏它。感谢@Paulw11 的想法。一旦我找到一个优雅的解决方案,我会在这里更新它

标签: ios swift swift5 combine


【解决方案1】:

是的,有办法。您可以简单地使用 Combine 的 flatMap 运算符:

将来自上游发布者的所有元素转换为新发布者,最多可指定您指定的最大发布者数量。

我的工作示例:

import Foundation
import Combine

class User: ObservableObject {
    @Published var name: String

    init(name: String) {
        self.name = name
    }

    var debugDescription: String {
        return self.name
    }
}

class AccountService {
    @Published var user: User? = nil
    var userCancel: AnyCancellable?
    
    init() {
        self.userCancel = self.$user
            .filter { $0 != nil }
            .flatMap { $0!.$name }
            .sink(receiveValue: { name in
                print(name)
            })
    }

    func setUseru(user: User) {
        self.user = user
    }

    func changeUserName(name: String) {
        self.user?.name = name
    }
}

let x = AccountService()
x.setUseru(user: User(name: "Philipp"))
x.changeUserName(name: "Tom")
x.setUseru(user: User(name: "Anna"))

程序的输出将是:

Philipp
Tom
Anna

【讨论】:

  • 我相信您可以通过一次调用 compactMap 来替换过滤器和 flatMap,例如.compactMap { $0?.name }
  • 不,不幸的是,这与我的示例不同。 compactMap 的输出将只有“Philipp, Anna”,缺少“Tom”。那是因为compactMap 删除了 nil 结果,但flatMap 返回了一个新的发布者。
猜你喜欢
  • 2023-01-30
  • 2018-11-27
  • 1970-01-01
  • 2020-05-31
  • 2022-08-25
  • 2020-08-18
  • 2019-10-03
  • 1970-01-01
  • 2023-01-11
相关资源
最近更新 更多