在命令式Swift中,通常使用计算属性在不复制状态的情况下方便地访问数据。
比方说,我将此类用于命令式MVC:
@Published
如果我想使用Combine创建一个等效的反应,例如为了与SwiftUI一起使用,我可以轻松地将Publisher
添加到存储的属性中以生成 @Published var userIsLoggedIn: Bool { // Error: Property wrapper cannot be applied to a computed property
currentUser != nil
}
,但不能为计算的属性添加。
class ReactiveUserManager1: ObservableObject {
@Published private(set) var currentUser: User? {
didSet {
userIsLoggedIn = currentUser != nil
}
}
@Published private(set) var userIsLoggedIn: Bool = false
// ...
}
我可以想到多种解决方法。我可以改为存储我的计算属性,并保持其更新。
选项1:使用属性观察器:
Subscriber
选择2:在自己的课堂上使用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)
}
// ...
}
:
Publisher
但是,这些变通办法不如计算属性好。它们复制状态,并且不会同时更新两个属性。
将/es/
添加到Combine中的计算属性中等同于什么?
答案 0 :(得分:8)
对于基于@Published
属性的计算属性,您无需执行任何操作。您可以像这样使用它:
class UserManager: ObservableObject {
@Published
var currentUser: User?
var userIsLoggedIn: Bool {
currentUser != nil
}
}
@Published
的{{1}}属性包装器中发生的事情是,它将在更改时调用currentUser
中的objectWillChange.send()
。 SwiftUI视图并不关心ObservedObject
的哪些属性已更改,它只会重新计算视图并在必要时重新绘制。
工作示例:
@ObservedObject
和一个SwiftUI演示视图:
class UserManager: ObservableObject {
@Published
var currentUser: String?
var userIsLoggedIn: Bool {
currentUser != nil
}
func logOut() {
currentUser = nil
}
func logIn() {
currentUser = "Demo"
}
}
答案 1 :(得分:5)
创建一个新发布者,该发布者已订阅要跟踪的媒体资源。
@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
来跟踪多个属性。
@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()
}()
答案 2 :(得分:1)
您应该在ObservableObject中声明一个 PassthroughSubject :
class ReactiveUserManager1: ObservableObject {
//The PassthroughSubject provides a convenient way to adapt existing imperative code to the Combine model.
var objectWillChange = PassthroughSubject<Void,Never>()
[...]
}
在您的 @Published var 的didSet(可能更好的willSet)中,您将使用称为 send()
的方法class ReactiveUserManager1: ObservableObject {
//The PassthroughSubject provides a convenient way to adapt existing imperative code to the Combine model.
var objectWillChange = PassthroughSubject<Void,Never>()
@Published private(set) var currentUser: User? {
willSet {
userIsLoggedIn = currentUser != nil
objectWillChange.send()
}
[...]
}
中进行检查
答案 3 :(得分:0)
如何使用下游?
lazy var userIsLoggedInPublisher: AnyPublisher = $currentUser
.map{$0 != nil}
.eraseToAnyPublisher()
通过这种方式,订阅将从上游获取元素,然后您可以使用sink
或assign
来实现didSet
的想法。
答案 4 :(得分:0)
扫描(: :) 通过向闭包提供当前元素以及闭包返回的最后一个值,来转换上游发布者中的元素。
您可以使用scan()获取最新和当前值。 示例:
@Published var loading: Bool = false
init() {
// subscriber connection
$loading
.scan(false) { latest, current in
if latest == false, current == true {
NotificationCenter.default.post(name: NSNotification.Name("userStateDidChange"), object: nil)
}
return current
}
.sink(receiveValue: { _ in })
.store(in: &subscriptions)
}
上面的代码等效于此:(减少合并)
@Published var loading: Bool = false {
didSet {
if oldValue == false, loading == true {
NotificationCenter.default.post(name: NSNotification.Name("userStateDidChange"), object: nil)
}
}
}