【问题标题】:@Published property wrapper not working on subclass of ObservableObject@Published 属性包装器不适用于 ObservableObject 的子类
【发布时间】:2019-12-28 04:00:00
【问题描述】:

我有一个符合@ObservableObject 协议的类,并使用它自己的变量和@Published 属性包装器创建了一个子类来管理状态。

似乎@published 属性包装器在使用子类时被忽略了。有谁知道这是否是预期行为以及是否有解决方法?

我正在运行 iOS 13 Beta 8 和 xCode Beta 6。

这是我所看到的一个例子。在 MyTestObject 上更新 TextField 时,Text 视图会正确更新为 aString 值。如果我更新MyInheritedObjectTextField,则另一个字符串值不会在文本视图中更新。

import SwiftUI

class MyTestObject: ObservableObject {
    @Published var aString: String = ""

}

class MyInheritedObject: MyTestObject {
    @Published var anotherString: String = ""
}

struct TestObserverWithSheet: View {
    @ObservedObject var myTestObject = MyInheritedObject()
    @ObservedObject var myInheritedObject = MyInheritedObject()

    var body: some View {
        NavigationView {
            VStack(alignment: .leading) {
                TextField("Update aString", text: self.$myTestObject.aString)
                Text("Value of aString is: \(self.myTestObject.aString)")

                TextField("Update anotherString", text: self.$myInheritedObject.anotherString)
                Text("Value of anotherString is: \(self.myInheritedObject.anotherString)")
            }
        }
    }
}

【问题讨论】:

  • 不要冒犯,但您发布的代码不包含任何建议子类化的内容。它在哪里?
  • 我可能混淆了术语,但 MyInheritedObject 类继承自 MyTestObject 类?
  • 我不确定我是否理解正确,或者我没有正确解释我的问题?代码已经创建了两个类,MyTestObject: ObservableObjectclass MyInheritedObject: MyTestObject。如果您复制粘贴我描述的确切代码,您可以测试并亲自查看self.myInheritedObject.anotherString 的值没有更新。
  • 啊!我的错。我错过了遗产。 :-) 我可能有答案。让我尝试一下,我会在几分钟后发布一些内容。

标签: ios swift inheritance swiftui ios13


【解决方案1】:

更新

这已在 iOS 14.5 和 macOS 11.3 中修复,ObservableObject 的子类将正确发布这些版本的更改。但请注意,当用户在任何较旧的次要操作系统版本上运行相同的应用程序时,都会出现原始问题。对于这些版本上使用的任何类,您仍然需要以下解决方法。


我发现这个问题的最佳解决方案如下:

objectWillChange 发布者声明BaseObservableObject

open class BaseObservableObject: ObservableObject {
    
    public let objectWillChange = ObservableObjectPublisher()

}

然后,要在您的子类中触发objectWillChange,您必须同时处理可观察类和值类型的更改:

class MyState: BaseObservableObject {

    var classVar = SomeObservableClass()
    var typeVar: Bool = false {
        willSet { objectWillChange.send() }
    }
    var someOtherTypeVar: String = "no observation for this"

    var cancellables = Set<AnyCancellable>()

    init() {
        classVar.objectWillChange // manual observation necessary
            .sink(receiveValue: { [weak self] _ in
                self?.objectWillChange.send()
            })
            .store(in: &cancellables)
    }
}

然后你可以继续子类化并在需要的地方添加观察:

class SubState: MyState {

    var subVar: Bool = false {
        willSet { objectWillChange.send() }
    }

}

如果根父类中已经包含 @Published 变量,您可以跳过在根父类中继承 BaseObservableObject,因为随后会合成发布者。但请注意,如果您从根父类中删除最终的 @Published 值,子类中的所有 objectWillChange.send() 都将静默停止工作。

很遗憾不得不经过这些步骤,因为以后添加变量后很容易忘记添加观察。希望我们能得到更好的官方修复。

【讨论】:

    【解决方案2】:

    iOS 14.5 解决了这个问题。

    合并

    已解决的问题

    在符合 ObservableObject 的类型的子类中使用 Published 现在可以正确发布更改。 (71816443)

    【讨论】:

    • 很高兴知道这终于奏效了。不幸的是,如果您对旧 iOS 版本的测试不是非常小心,这也将成为微妙错误的来源 - 子类中的 @Published 现在可以在 iOS 14.5 上运行,但在旧 iOS 14.* 版本中会失败。
    【解决方案3】:

    根据我的经验,只需将子类 objectWillChange 与基类的 objectWillChange 链接起来,如下所示:

    class GenericViewModel: ObservableObject {
        
    }
    
    class ViewModel: GenericViewModel {
        @Published var ...
        private var cancellableSet = Set<AnyCancellable>()
        
        override init() {
            super.init()
            
            objectWillChange
                .sink { super.objectWillChange.send() }
                .store(in: &cancellableSet)
        }
    }
    

    【讨论】:

      【解决方案4】:

      这是因为 ObservableObject 是一个协议,所以你的子类必须符合协议,而不是你的父类

      例子:

      class MyTestObject {
          @Published var aString: String = ""
      
      }
      
      final class MyInheritedObject: MyTestObject, ObservableObject {
          @Published var anotherString: String = ""
      }
      

      现在,类和子类的@Published 属性都将触发视图事件

      【讨论】:

      • 但是如果你想让MyTestObjectMyInheritedObject在不同的视图中被观察到,这个解决方案没有帮助,因为MyTestObject不再符合ObservableObject
      【解决方案5】:

      当你的类不是ObservableObject的直接子类时也会发生这种情况:

      class YourModel: NSObject, ObservableObject {
      
          @Published var value = false {
              willSet {
                  self.objectWillChange.send()
              }
          }
      }
      

      【讨论】:

        【解决方案6】:

        终于找到了解决这个问题的办法。如果从子类中移除属性包装器,并在变量上调用基类 objectWillChange.send(),则状态会正确更新。

        注意:不要在子类上重新声明let objectWillChange = PassthroughSubject&lt;Void, Never&gt;(),因为这会再次导致状态不能正确更新。

        我希望这将在未来的版本中得到修复,因为 objectWillChange.send() 需要维护很多样板文件。

        这是一个完整的示例:

            import SwiftUI
        
            class MyTestObject: ObservableObject {
                @Published var aString: String = ""
        
            }
        
            class MyInheritedObject: MyTestObject {
                // Using @Published doesn't work on a subclass
                // @Published var anotherString: String = ""
        
                // If you add the following to the subclass updating the state also doesn't work properly
                // let objectWillChange = PassthroughSubject<Void, Never>()
        
                // But if you update the value you want to maintain state 
                // of using the objectWillChange.send() method provided by the 
                // baseclass the state gets updated properly... Jaayy!
                var anotherString: String = "" {
                    willSet { self.objectWillChange.send() }
                }
            }
        
            struct MyTestView: View {
                @ObservedObject var myTestObject = MyTestObject()
                @ObservedObject var myInheritedObject = MyInheritedObject()
        
                var body: some View {
                    NavigationView {
                        VStack(alignment: .leading) {
                            TextField("Update aString", text: self.$myTestObject.aString)
                            Text("Value of aString is: \(self.myTestObject.aString)")
        
                            TextField("Update anotherString", text: self.$myInheritedObject.anotherString)
                            Text("Value of anotherString is: \(self.myInheritedObject.anotherString)")
                        }
                    }
                }
            }
        

        【讨论】:

        • 您是否将此作为错误提交?你能把 rdar 号码贴在这里让我上当吗?
        • 哦。我的。上帝。我已经把头发扯掉了一个星期,并以各种方式进行重构。这。这就是问题。非常感谢!!!!
        • 这是一个错误吗?这是一个完整的 PITA
        • 使这个答案起作用还有另一个要求:基类至少需要一个@Published var,否则 objectWillChange.send() 不会做任何事情。
        • 该死的,偶然发现了同样的问题。如果你想制作一个真正的应用程序而不是一些点击率教程,那么 SwiftUI 现在真是一团糟。
        猜你喜欢
        • 1970-01-01
        • 2020-11-21
        • 1970-01-01
        • 2019-12-22
        • 1970-01-01
        • 2021-05-13
        • 1970-01-01
        • 2020-03-02
        • 1970-01-01
        相关资源
        最近更新 更多