不符合协议 BindableObject - Xcode 11 Beta 4

Posted

技术标签:

【中文标题】不符合协议 BindableObject - Xcode 11 Beta 4【英文标题】:Does not conform to protocol BindableObject - Xcode 11 Beta 4 【发布时间】:2019-07-18 05:28:27 【问题描述】:

玩弄那里的例子。找到一个项目,该项目的类为 bindableobject 并且没有给出任何错误。现在 Xcode 11 beta 4 已经发布,我得到了错误:

类型“UserSettings”不符合协议“BindableObject”

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

typealias PublisherType = <#type#>

它希望你填写类型。

类型是什么?

class UserSettings: BindableObject 

    let didChange = PassthroughSubject<Void, Never>()

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

【问题讨论】:

【参考方案1】:

Beta 4 Release notes 说:

BindableObject 协议的要求现在是 willChange 而不是 didChange,现在应该在对象更改之前发送 比改变之后。此更改允许改进的合并 更改通知。 (51580731)

您需要将代码更改为:

class UserSettings: BindableObject 

    let willChange = PassthroughSubject<Void, Never>()

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

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

BindableObject 被 ObservableObject 协议从 结合框架。 (50800624)

您可以通过定义一个手动符合 ObservableObject 在对象更改之前发出的 objectWillChange 发布者。 但是,默认情况下,ObservableObject 会自动合成 objectWillChange 并在任何 @Published 属性更改之前发出。

@ObjectBinding 被 @ObservedObject 替换。

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


struct MyView: View 
    @ObservedObject var settings: UserSettings

【讨论】:

? — 我想知道你在调用这个之后需要多长时间更改值?而且,如果您在调用 willChange 后从不更改值会发生什么? @jasonz 是的,我也很困惑。但就我们使用 willSet 调用 willChange.send() 而言,我们应该是安全的。该代码在设置值之前立即执行。 在某种程度上看起来与setNeedLayout() 相同。进行更改并告诉系统 UI 需要稍后更新,以便聚合所有更改并避免每个属性集多次刷新。 @LudovicLandry,是的,但我认为 jasonz 担心的是,与 beta3 不同,您现在需要在更改发生之前通知更改,而不是之后。 使用 Beta 5 @Published 属性包装器摆脱 willSet / willChange.send() 样板可以缓解我之前的担忧。【参考方案2】:

在 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/

【讨论】:

【参考方案3】:

SwiftUI 和 Combine 是在 WWDC 2019 上宣布的两个新框架。这两个框架在 WWDC 2019 上受到了很多关注,这些技术在会议上的数量就是证明。

SwiftUI 被引入为

一种革命性的新方法,可以更快地构建更好的应用程序。

组合被描述为

随着时间的推移处理值的统一声明框架

在初始版本和现在(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)
    

通过画布预览的应用程序如下所示:

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

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

根据下面的类图,视图具有对参与者对象的引用。

虽然这个例子使用了Combine,但它并不是很明显。没有提及发布者或订阅者。怎么回事?

答案:查看类层次结构可以填补缺失的空白。下面的类图解释了全图(点击图片查看更详细)。

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

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

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

换句话说,该属性是一个订阅者(来自组合框架)。 Actor 是(通过使用属性包装器)是一个订阅者,它订阅一个 Publisher,但是在这种情况下,Publisher 是什么?

“可观察对象”本身并不是发布者,而是拥有发布者。 ActorViewModel 符合ObservableObject 协议。通过这样做,它通过扩展(框架在 ObservableObject 协议上提供)提供了一个名为 objectWillChange 的发布者属性。这个objectWillChange 属性属于PassthroughSubject 类型,它是Publisher 协议的具体类型。 passthrough 主题有一个名为send 的属性,它是一种发布者方法,用于向任何订阅者发送数据。所以名为“objectWillChange”的属性是Publisher

概括地说,Subscriber 是 ContentView 类中名为 actor 的属性,Publisher 是 ActorViewModel 类中名为 objectWillChange 的属性。订阅者 订阅Publisher 的需求如何? “@ObservedObject”属性包装器本身就是订阅者,因此它必须订阅发布者。但是视图如何发现发送给订阅者的更改?这是由我们从未见过的 SwiftUI 框架处理的。

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

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

订阅者 --> 订阅发布者 --> 然后发布者发布更改 --> 订阅者收到的更改

参考文献:

Blog Article WWDC 2019 Session 204 WWDC 2019 Session 226

【讨论】:

以上是关于不符合协议 BindableObject - Xcode 11 Beta 4的主要内容,如果未能解决你的问题,请参考以下文章

WPF - 为啥在 XAML 中设置 Binding BindableObject.Text 时 SetProperty() 不触发 CanExecute?

SwiftUI 中来自 BindableObject 的去抖动方法调用

@BindableObject 对 didChange.send() 的异步调用不会使其视图无效(并且永远不会更新)

协议扩展,不符合协议

UITableView 不符合协议

Codable : 不符合协议'Decodable'