Type 'any ProtocolName' cannot conform to '(ProtocolName's Inherited Protocol)' [duplicate]
Asked Answered
C

1

11

I have a ProtocolName that conforms to ObservableObject. I then have ClassName that conforms to ProtocolName.

However, when I try to use @ObservedObject serviceName: ProtocolName = ClassName, it fails to build with the following message: "Type 'any ProtocolName' cannot conform to 'ObservableObject'"

What am I doing wrong and how can I fix this?

Example:

protocol ProtocolName: ObservableObject {
    let random: String
}

class ClassName: ProtocolName {
    let random: String
}

@ObservedObject serviceName: ProtocolName = ClassName()
Charily answered 19/9, 2022 at 16:2 Comment(2)
Which version of Swift are you using? Can you provide a minimal example that demonstrates the problem (e.g. in a Playground)Staffard
@ScottThompson I'm using Swift 5.7 and updated the post accordinglyCharily
S
27

When you write a line like this:

@ObservedObject var observeMe: ProtocolName

You are saying that observeMe is a box that can contain any kind of thing that conforms to the ProtocolName protocol"

The type of that box is any ProtocolName it's called an existential.

But the box itself does NOT conform to ProtocolName(and by extension does not conform to ObservableObject). The thing inside the box does, but the box itself does not.

So the compiler complains that the existential whose type is any ProtocolName is not an ObservableObject

You can make the box even more obvious and explicit using the any ProtocolName syntax:

import SwiftUI
import Combine

protocol ProtocolName: ObservableObject {
    var someValue: Int { get }
}

class MyClass: ProtocolName {
    @Published var someValue: Int = 42
}

struct SomeStruct : View {
    @ObservedObject var observeMe: any ProtocolName = MyClass()
    
    var body: some View {
        Text("Hello.")
    }
}

And you'll still see the error.

To solve the problem your @ObservedObject has to be a concrete type that conforms to ProtocolName:

import SwiftUI
import Combine

protocol ProtocolName: ObservableObject {
    var someValue: Int { get }
}

class MyClass: ProtocolName {
    @Published var someValue: Int = 42
}

struct SomeStruct : View {
    @ObservedObject var observeMe: MyClass = MyClass()
    
    var body: some View {
        Text("Hello.")
    }
}

let myView = SomeStruct()

Or you can add a type parameter to your view so that when the view is created there is a specific type that conforms to the protocol that is used for the view:

import SwiftUI
import Combine

protocol ProtocolName: ObservableObject {
    var someValue: Int { get set }
}

class MyClass: ProtocolName {
    @Published var someValue: Int = 42
}

struct SomeStruct<T : ProtocolName> : View {
    @ObservedObject var observeMe:T
    
    var body: some View {
        Text("Hello.")
        Button(action: {observeMe.someValue = 3}) {
            Text("Button")
        }
    }
}

let myView = SomeStruct(observeMe: MyClass())
Staffard answered 19/9, 2022 at 16:27 Comment(6)
Since the solution is to make the ObservedObject property a concrete type, is there any way to make sure the ObservedObject property doesn't just conform to the MyClass implementation and perhaps other implementations that conform to ProcolName?Charily
At some point you have to have an explicit type there. The last code example shows using a type parameter.Staffard
I think the last solution type parameter is best.Bounden
These solutions defeats the purpose the dependency Inversion Principle that has be traditionally used for dependency injection making it possible to use mock version of a dependency just changing the dependency in one single place. According to these solutions if it is needed to change the concrete class used, the only way is to manually change all the injected property initialiser every time the view is used making the code brittle. Protocols were useful just for that reason and could allow to polymorphically use, at run-time, the required concrete type. Why is this not possible anymore?Cru
@Cru I don't know what you mean by "not possible anymore". The any Protocol syntax makes existentials explicit, but the semantics haven't changed. (the compiler errors got better though). Dependency Inversion is a high-level, logical construct. This syntax allows code that does, or does not, violate the principle. In the last example, the code constructing the view identifies the concrete implementation of the protocol. The view itself only knows about the protocol. Properly applied, there should be no Dependency Inversion there.Staffard
What I mean is that without the constraints of SwiftUI you can "program to interface" that means the View would only know about the viewModel interface and you could decide the a concrete class to use only once,at the root of the App. This allowed to easily create "real" dependencies or mock ones for unit tests / demo apps without touching the rest of the code.Now from what I am seeing swift UI forces the programmer to declare the concrete instance of a dependency everywhere it is used.Isn't this a big problem as it makes mocking the presentation layer logic (view model) not possible anymore?Cru

© 2022 - 2024 — McMap. All rights reserved.