Оболочка @Published свойства не работает на подклассе ObservableObject

У меня есть класс, соответствующий протоколу @ObservableObject, и я создал из него подкласс со своей собственной переменной с помощью оболочки свойств @Published для управления состоянием.

Кажется, что обертка свойства @published игнорируется при использовании подкласса. Кто-нибудь знает, если это ожидаемое поведение и есть ли обходной путь?

Я использую iOS 13 Beta 8 и xCode Beta 6.

Вот пример того, что я вижу. При обновлении TextField на MyTestObject текстовое представление корректно обновляется значением aString. Если я обновлю MyInheritedObjectTextField значение anotherString не обновляется в текстовом представлении.

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)")
            }
        }
    }
}

6 ответов

Решение

Наконец-то разобрался с решением / обходом этой проблемы. Если вы удалите оболочку свойства из подкласса и вызовете базовый класс objectWillChange.send() для переменной, состояние будет обновлено должным образом.

ПРИМЕЧАНИЕ: не переопределять let objectWillChange = PassthroughSubject<Void, Never>() на подклассе, поскольку это снова приведет к тому, что состояние не будет обновляться должным образом.

Я надеюсь, что это будет исправлено в будущих версиях, так как 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)")
                }
            }
        }
    }

iOS 14.5 решает эту проблему.

Комбинировать

Решенные вопросы

Использование опубликованного в подклассе типа, соответствующего ObservableObject, теперь правильно публикует изменения. (71816443)

Это связано с тем, что ObservableObject - это протокол, поэтому ваш подкласс должен соответствовать протоколу, а не вашему родительскому классу.

Пример:

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

}

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

Теперь свойства @Published как для класса, так и для подкласса будут запускать события просмотра

Лучшее решение этой проблемы, которое я нашел, заключается в следующем:

Объявить издателю:

      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() }
    }

}

Вы можете пропустить наследование BaseObservableObjectв корневом родительском классе, если этот класс уже содержит переменные, поскольку затем синтезируется издатель. Но будьте осторожны, если уберете финальную @Published значение из корневого родительского класса, все objectWillChange.send() в подклассах молча перестанут работать.

Очень жаль, что приходится проходить эти шаги, потому что очень легко забыть добавить наблюдение, если вы добавите переменную в будущем. Надеюсь, мы получим лучшее официальное исправление.

Это также происходит, когда ваш класс не является напрямую подклассом ObservableObject:

class YourModel: NSObject, ObservableObject {

    @Published var value = false {
        willSet {
            self.objectWillChange.send()
        }
    }
}

По моему опыту, просто соедините подкласс 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)
    }
}
Другие вопросы по тегам