3
protocol ExampleStore: ObservableObject {
    var text: String { get }
}

class ConcreteExampleStore: ExampleStore {
    @Published var text: String = ""
}

struct ExampleView: View {
    @ObservedObject private var store: any ExampleStore

    init(store: any ExampleStore) {
        self.store = store
    }

    var body: some View {
        Text(store.text)
    }
}

Im exploring the new features of Swift 5.7 and was trying to make a Store protocol which I would use as a type inside my View. I know that this would not be possible in previous versions of Swift since the ObservableObject protocol which ExampleStore conforms to has an associatedType and therefore the workaround was to define a generic parameter in the View which conforms to ExampleStore protocol, but since 5.7 those protocols should be usable as existential types?

I'm getting the following error: Type 'any ExampleStore' cannot conform to 'ObservableObject'. It is strange that the message appears on the line where the store is declared as a property of the View, can anyone make sense of this error message? Thanks in advance :)

Screenshot of the code and error message

hydro1337x
  • 95
  • 5

1 Answers1

1

This workaround works although could not generalize, so needs to be defined for each SomeProtocol.

protocol SomeProtocol: ObservableObject {
    ...
}

ObservableAnySomeProtocol is concrete class that conforms to ObservableObject and serves as a wrapper to pass the change events of source object.

@propertyWrapper
@dynamicMemberLookup
class ObservableAnySomeProtocol: ObservableObject {

    var wrappedValue: any SomeProtocol
    var cancellable: AnyCancellable?

    init(wrappedValue: any SomeProtocol) {
        self.wrappedValue = wrappedValue
        self.cancellable = wrappedValue.objectWillChange.sink(receiveValue: { [unowned self] in
            self.objectWillChange.send()
        })
    }

    subscript<T>(dynamicMember keyPath: WritableKeyPath<any SomeProtocol, T>) -> T {
        get {
            wrappedValue[keyPath: keyPath]
        }
        set {
            wrappedValue[keyPath: keyPath] = newValue
        }
    }
}

Then in a client view:

struct MyView: View {
    
    @ObservedObject @ObservableAnySomeProtocol var someObject: any SomeProtocol

    var body: some View {
        ...
    }
}
user1668604
  • 431
  • 2
  • 11