不符合协议 BindableObject - Xcode 11 Beta 4

Does not conform to protocol BindableObject - Xcode 11 Beta 4

尝试一些例子。找到一个 class 是 bindableobject 的项目,它没有给出任何错误。现在 Xcode 11 beta 4 已经发布,我收到错误消息:

Type 'UserSettings' does not conform to protocol 'BindableObject'

它有一个修复错误的按钮,当你点击它时,它会添加

typealias PublisherType = <#type#>

它希望您填写类型。

类型是什么?

class UserSettings: BindableObject {

    let didChange = PassthroughSubject<Void, Never>()

    var score: Int = 0 {
        didSet {
            didChange.send()
        }
    }
}

Beta 4 Release notes 说:

The BindableObject protocol’s requirement is now willChange instead of didChange, and should now be sent before the object changes rather than after it changes. This change allows for improved coalescing of change notifications. (51580731)

您需要将代码更改为:

class UserSettings: BindableObject {

    let willChange = PassthroughSubject<Void, Never>()

    var score: Int = 0 {
        willSet {
            willChange.send()
        }
    }
}

Beta 5 中他们再次更改它。 这次他们一起弃用了 BindableObject!

BindableObject is replaced by the ObservableObject protocol from the Combine framework. (50800624)

You can manually conform to ObservableObject by defining an objectWillChange publisher that emits before the object changes. However, by default, ObservableObject automatically synthesizes objectWillChange and emits before any @Published properties change.

@ObjectBinding is replaced by @ObservedObject.

class UserSettings: ObservableObject {
    @Published var score: Int = 0
}

struct MyView: View {
    @ObservedObject var settings: UserSettings
}

在 Xcode 11.X 中,我在 Xcode 11.2.1、11.3.

中验证没问题

BindableObject 更改为 ObservableObject。

ObjectBinding 现在是 ObservedObject。

didChange 应该改为 objectWillChange。

List(dataSource.pictures, id: .self) { }

您现在也可以摆脱 did/willChange 发布者和 .send 代码,只制作图片 @Published

其余的将为您自动生成。

例如:

import SwiftUI
import Combine
import Foundation

class RoomStore: ObservableObject {
    @Published var rooms: [Room]
    init(rooms: [Room]) {
        self.rooms = rooms
    }
}

struct ContentView: View {
    @ObservedObject var store = RoomStore(rooms: [])
}

参考:https://www.reddit.com/r/swift/comments/cu8cqk/getting_the_errors_pictured_below_when_try_to/

SwiftUI 和 Combine 是在 WWDC 2019 上宣布的两个新框架。这两个框架在 WWDC 2019 上受到了很多关注,从这些框架的会话数量可以看出特色技术。

SwiftUI 被介绍为

a revolutionary, new way to build better apps, faster.

合并被描述为

a unified declarative framework for processing values over time

从初始版本到现在(2020 年 5 月,Swift 5.2),发生了一些变化。 SwiftUI 和 Combine 的新手,如果看过 WWDC 视频,可能会对这两个框架如何协同工作产生一些疑问。

Combine定义了两个接口:Publisher和Subscriber。发布者向订阅者发送事件。请参见下面的时序图。

如果您在SwiftUI中启动应用程序,然后添加combine,将不会提及Publisher 或Subscriber,这两个主要玩家需要使用Combine。考虑下面这个非常简单的示例应用程序。

import SwiftUI
import Combine
import SwiftUI

final class ActorViewModel: ObservableObject {
    var name : String
    private var imageUrl : URL?
    //@Published
    private (set) var image : Image = Image(systemName: "photo") {
        willSet {
            DispatchQueue.main.async {
                self.objectWillChange.send()
            }
        }
    }

    init(name: String, imageUrl: URL?) {
        self.name = name
        self.imageUrl = imageUrl
        self.fetchImage()
    }

    private func fetchImage() {
        guard nil != self.imageUrl,
            String() != self.imageUrl!.absoluteString else { return }
        let task = URLSession.shared.dataTask(with: self.imageUrl!) { (data, response, error) in

            guard nil == error , nil != response, nil != data,
                let uiImage = UIImage(data: data!) else { return }
                self.image = Image(uiImage: uiImage)

        }
        task.resume()
    }
}

struct ContentView: View {
    @ObservedObject var actor : ActorViewModel

    var body: some View {
        HStack {
            actor.image
                .resizable()
                .aspectRatio(contentMode: ContentMode.fit)
                .frame(width: 60, height: 60)
            Text(actor.name)
        }

    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        let actor = ActorViewModel(name: "Mark Hammill",
                                   imageUrl: URL(string: "https://m.media-amazon.com/images/M/MV5BOGY2MjI5MDQtOThmMC00ZGIwLWFmYjgtYWU4MzcxOGEwMGVkXkEyXkFqcGdeQXVyMzM4MjM0Nzg@._V1_.jpg"))
        return ContentView(actor: actor)
    }
}

通过 canvas 进行的应用预览将如下所示:

该应用程序使用列表视图来显示演员的姓名和图像。只有两个 class 需要考虑:

  1. ContentView -- SwiftUI 视图子class
  2. ActorViewModel -- ContentView 的数据源(称为 ViewModel,因为它在 MVVM 中扮演 VM 的角色)

根据下面的 class 图表,视图具有对 actor 对象的引用。

尽管此示例使用的是 Combine,但并不是很明显。没有提及发布者或订阅者。这是怎么回事?

答案:查看 class 层次结构可以填补缺失的空白。下面的 class 图表解释了完整的图片(单击图片可以更详细地查看)。

查阅 Apple 文档提供了这些类型的定义:

  • ObservedObject:一种 属性 包装器类型,订阅可观察对象并在可观察对象发生变化时使视图无效。
  • ObservableObject:一种对象类型,在对象发生变化之前发出一个发布者。默认情况下,ObservableObject 合成一个 objectWillChange 发布者,该发布者在其任何 @Published 属性更改之前发出更改的值。
  • objectWillChange: 在对象改变之前发出的发布者。
  • PassthroughSubject:向下游订阅者广播元素的主题。作为 Subject 的具体实现,PassthroughSubject 提供了一种将现有命令式代码适配为 Combine 模型的便捷方式。

首先,考虑 @ObservedObject 的含义。这是一个 属性 包装器。 属性 包装器减少了代码重复,并在声明隐藏 属性 存储和定义方式的属性时允许使用简洁的语法。在这种情况下,"Observed Object" 是一个 属性,它观察另一个对象。

换句话说,属性 是一个 订阅者(来自 Combine Framework)。参与者(通过使用 属性 包装器)是一个订阅者,它订阅了一个 发布者 ,但在这种情况下发布者是什么?

"Observable Object"本身不是发布者,而是有一个发布者。 ActorViewModel 符合 ObservableObject 协议。通过这样做,它提供了一个发布者 属性,通过扩展(框架在 ObservableObject 协议上提供)称为 objectWillChange。这个objectWillChange属性是PassthroughSubject类型,是Publisher协议的具体类型。传递主题有一个名为 send 的 属性,这是一种用于向任何订阅者发送数据的发布者方法。所以被称为 "objectWillChange" 的 属性 是 Publisher.

回顾一下,订阅者是来自 ContentView class 的 属性 调用 actor,发布者是来自 ActorViewModel 的 属性 objectWillChange class。订阅者 订阅 发布者 的需求如何? “@ObservedObject”属性 包装器本身就是一个订阅者,因此它必须订阅发布者。但是视图如何找到发送给订阅者的更改呢?这是由我们从未见过的 SwiftUI 框架处理的。

要点:我们无需担心将视图订阅到 Publisher。另一方面,我们确实需要担心确保发布者在某些内容即将发生变化时通知订阅者。当从远程服务器获取图像并将数据转换为图像对象时,我们调用 objectWillChange.send() 通知视图。一旦订阅者收到来自发布者的通知,即某些内容即将/已经更改,它会使视图无效(这会导致视图自身重绘)。

总结 SwiftUI 使用 ObservedObject PropertyWrapper 的方式在表面上并没有泄露 Combine 甚至存在于等式中的事实。但是通过检查 ObservedObject 和 ObservableObject,揭示了底层的 Combine 框架,以及设计模式:

订阅者 --> 订阅发布者 --> 然后发布更改 --> 由订阅者接收

参考文献: