Ios 等同于使用在Swift Combine中发布的@计算属性?

Ios 等同于使用在Swift Combine中发布的@计算属性?,ios,swift,swiftui,reactive,combine,Ios,Swift,Swiftui,Reactive,Combine,在命令式Swift中,通常使用计算属性来提供对数据的方便访问,而无需复制状态 假设我有一个为强制MVC使用而设计的类: 类命令管理器{ private(set)var currentUser:用户{ 迪塞特{ 如果oldValue!=currentUser{ NotificationCenter.default.post(名称:NSNotification.name(“userStateDidChange”),对象:nil) //收到此通知的观察者随后可能会检查currentUser或userI

在命令式Swift中,通常使用计算属性来提供对数据的方便访问,而无需复制状态

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

类命令管理器{
private(set)var currentUser:用户{
迪塞特{
如果oldValue!=currentUser{
NotificationCenter.default.post(名称:NSNotification.name(“userStateDidChange”),对象:nil)
//收到此通知的观察者随后可能会检查currentUser或userIsLoggedIn的最新状态
}
}
}
var userIsLoggedIn:Bool{
当前用户!=nil
}
// ...
}
如果我想使用Combine创建一个反应性等价物,例如用于SwiftUI,我可以轻松地将
@Published
添加到存储的属性中,以生成
Publisher
s,但不能用于计算属性

@Published var userIsLoggedIn:Bool{//Error:Property包装器无法应用于计算属性
当前用户!=nil
}
我可以想到各种各样的解决办法。我可以将我的计算属性存储起来并保持更新

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

类ReactiveUserManager1:observeObject{
@已发布的专用(集)变量currentUser:用户{
迪塞特{
userIsLoggedIn=currentUser!=nil
}
}
@发布的私有(set)var userIsLoggedIn:Bool=false
// ...
}
选项2:在我自己的班级中使用
订阅者

类ReactiveUserManager2:observeObject{
@已发布的专用(集)变量currentUser:用户?
@发布的私有(set)var userIsLoggedIn:Bool=false
private var subscribers=Set()
init(){
$currentUser
.map{$0!=nil}
.assign(给:\.userIsLoggedIn,在:self上)
.store(位于订阅服务器(&S)
}
// ...
}
但是,这些变通方法不如计算属性那么优雅。它们复制状态,并且不会同时更新两个属性


将发布者添加到Combine中的计算属性中的适当等价物是什么?

使用下游如何

lazy var userIsLoggedInPublisher:AnyPublisher=$currentUser
.map{$0!=nil}
.删除任何发布者()
通过这种方式,订阅将从上游获取元素,然后您可以使用
sink
assign
执行
didSet
想法。

您应该在ObserveObject中声明一个PassthroughSubject:

class ReactiveUserManager1: ObservableObject {

    //The PassthroughSubject provides a convenient way to adapt existing imperative code to the Combine model.
    var objectWillChange = PassthroughSubject<Void,Never>()

    [...]
}
类ReactiveUserManager1:observeObject{
//PassthroughSubject提供了一种方便的方法,使现有的命令式代码适应联合收割机模型。
var objectWillChange=PassthroughSubject()
[...]
}
在@Published var的didSet(willSet可能更好)中,您将使用一个名为send()的方法

类ReactiveUserManager1:observeObject{
//PassthroughSubject提供了一种方便的方法,使现有的命令式代码适应联合收割机模型。
var objectWillChange=PassthroughSubject()
@已发布的专用(集)变量currentUser:用户{
意志{
userIsLoggedIn=currentUser!=nil
objectWillChange.send()
}
[...]
}
您可以在扫描(:)中检查它 通过向闭包提供当前元素以及闭包返回的最后一个值,转换上游发布服务器中的元素

可以使用scan()获取最新和当前的值。 例如:

上述代码相当于:(较少的联合收割机)


创建一个新的发布者,订阅要跟踪的属性。

@公布的var速度:双=88
lazy var canTimeTravel:AnyPublisher={
$speed
.map({$0>=88})
.删除任何发布者()
}()

然后,您将能够像查看
@已发布的
属性一样观察它

private var subscriptions=Set()
重写func viewDidLoad(){
super.viewDidLoad()
sourceOfTruthObject.$canTimeTravel.sink{[弱自我](canTimeTravel)in
//做点什么…
})
.store(位于:&订阅中)
}
虽然不直接相关,但很有用,您可以通过
combinelateest
以这种方式跟踪多个属性

@已发布的变量阈值:Int=60
@已发布的var heartData=[Int]()
/**该出版商“观察”了“阈值”和“心脏数据”`
并从中获得一个值。
当其中一个值发生更改时,应更新该值*/
惰性变量状态:AnyPublisher={
$threshold
.CombineTest($heartData)
.map({threshold,heartData in
//用两个值计算“状态”
状态(heartData:heartData,阈值:阈值)
})
.receive(在:DispatchQueue.main上)
.删除任何发布者()
}()

对于基于
@Published
属性的计算属性,您无需执行任何操作。您可以这样使用它:

class UserManager: ObservableObject {
  @Published
  var currentUser: User?

  var userIsLoggedIn: Bool {
    currentUser != nil
  }
}

currentUser
@Published
属性包装中发生的情况是,它将调用
objectWillChange.send()
@ObservedObject
更改。SwiftUI视图不关心
@ObservedObject
的哪些属性已更改,它只会重新计算视图并在必要时重新绘制

工作示例:

class UserManager: ObservableObject {
  @Published
  var currentUser: String?

  var userIsLoggedIn: Bool {
    currentUser != nil
  }

  func logOut() {
    currentUser = nil
  }

  func logIn() {
    currentUser = "Demo"
  }
}
和SwiftUI演示视图:

struct ContentView: View {

  @ObservedObject
  var userManager = UserManager()

  var body: some View {
    VStack( spacing: 50) {
      if userManager.userIsLoggedIn {
        Text( "Logged in")
        Button(action: userManager.logOut) {
          Text("Log out")
        }
      } else {
        Text( "Logged out")
        Button(action: userManager.logIn) {
          Text("Log in")
        }
      }
    }
  }
}
计算属性的可能重复是一种派生属性。它们的值取决于依赖项的值。仅出于这个原因,可以说它们永远不会像
可观察对象那样工作。您固有地假设
可观察对象
对象
class UserManager: ObservableObject {
  @Published
  var currentUser: User?

  var userIsLoggedIn: Bool {
    currentUser != nil
  }
}

class UserManager: ObservableObject {
  @Published
  var currentUser: String?

  var userIsLoggedIn: Bool {
    currentUser != nil
  }

  func logOut() {
    currentUser = nil
  }

  func logIn() {
    currentUser = "Demo"
  }
}
struct ContentView: View {

  @ObservedObject
  var userManager = UserManager()

  var body: some View {
    VStack( spacing: 50) {
      if userManager.userIsLoggedIn {
        Text( "Logged in")
        Button(action: userManager.logOut) {
          Text("Log out")
        }
      } else {
        Text( "Logged out")
        Button(action: userManager.logIn) {
          Text("Log in")
        }
      }
    }
  }
}