在命令式Swift中,通常使用计算(computed)属性来方便地访问数据,而不复制状态.

假设我有一个为强制MVC使用而设计的类:

class ImperativeUserManager {
    private(set) var currentUser: User? {
        didSet {
            if oldValue != currentUser {
                NotificationCenter.default.post(name: NSNotification.Name("userStateDidChange"), object: nil)
                // Observers that receive this notification might then check either currentUser or userIsLoggedIn for the latest state
            }
        }
    }

    var userIsLoggedIn: Bool {
        currentUser != nil
    }

    // ...
}

如果我想用Combine创建一个react 性类似功能,例如与SwiftUI一起使用,我可以很容易地将@Published添加到存储属性中以生成Publishers,但不能用于计算(computed)属性.

    @Published var userIsLoggedIn: Bool { // Error: Property wrapper cannot be applied to a computed property
        currentUser != nil
    }

我可以想出各种各样的解决办法.我可以将我的计算(computed)属性存储起来并保持更新.

选项1:使用属性观察者:

class ReactiveUserManager1: ObservableObject {
    @Published private(set) var currentUser: User? {
        didSet {
            userIsLoggedIn = currentUser != nil
        }
    }

    @Published private(set) var userIsLoggedIn: Bool = false

    // ...
}

选项2:在我自己的课堂上使用Subscriber分:

class ReactiveUserManager2: ObservableObject {
    @Published private(set) var currentUser: User?
    @Published private(set) var userIsLoggedIn: Bool = false

    private var subscribers = Set<AnyCancellable>()

    init() {
        $currentUser
            .map { $0 != nil }
            .assign(to: \.userIsLoggedIn, on: self)
            .store(in: &subscribers)
    }

    // ...
}

然而,这些变通方法并不像计算(computed)属性那样优雅.它们复制状态,并且不会同时更新两个属性.

在Combine中,将Publisher添加到计算(computed)属性的正确类似功能是什么?

推荐答案

Create a new publisher subscribed to the property you want to track.

@Published var speed: Double = 88

lazy var canTimeTravel: AnyPublisher<Bool,Never> = {
    $speed
        .map({ $0 >= 88 })
        .eraseToAnyPublisher()
}()

然后,你将能够像观察你的@Published财产一样观察它.

private var subscriptions = Set<AnyCancellable>()


override func viewDidLoad() {
    super.viewDidLoad()

    sourceOfTruthObject.$canTimeTravel.sink { [weak self] (canTimeTravel) in
        // Do something…
    })
    .store(in: &subscriptions)
}

虽然没有直接的联系,但仍然很有用,你可以用combineLatest个这样追踪multiple个属性.

@Published var threshold: Int = 60

@Published var heartData = [Int]()

/** This publisher "observes" both `threshold` and `heartData`
 and derives a value from them.
 It should be updated whenever one of those values changes. */
lazy var status: AnyPublisher<Status,Never> = {
    $threshold
       .combineLatest($heartData)
       .map({ threshold, heartData in
           // Computing a "status" with the two values
           Status.status(heartData: heartData, threshold: threshold)
       })
       .receive(on: DispatchQueue.main)
       .eraseToAnyPublisher()
}()

Swift相关问答推荐

自定义完整屏幕封面动画

数据不会被ARC删除在swift'

在SwiftUI中使用自定义图像填充列表行的正确方法

如何在init()中使用setter/getters?

如何获取SCNCamera正在查看的网格点(SCNNode)的局部坐标和局部法线?

Swift-Can无法在AudioKit中找出简单的麦克风效果-文件链

如何使用变量 Select 哪个向量(?)在我的 struct 中密谋吗?

Swift String.firstIndex(of: "\n") 返回 nil,即使字符串有 \n

当变量首次用于其自己的初始化时,如何清除变量...在初始化之前使用错误?

我怎样才能缩短(提高效率)这段代码?

Swift 并发:@MainActor 对象上的通知回调

用逻辑运算符保护让

SwiftUI:异步网络请求后@State 值不更新

Swift Components.Url 返回错误的 URL

Vapor Swift 如何配置客户端连接超时

SwiftUI 文本被意外截断

单元测试和私有变量

从 NSData 对象在 Swift 中创建一个数组

如何更改弹出框的大小

由于编译器中的内部保护级别,无法访问框架 init 中的公共 struct