Not sure exactly how to phrase this or if it's possible, but essentially what I'm wanting to do is this. I would like to have a protocol that includes a function that takes a protocol as a parameter, but swap out that protocol with a different one, which also conforms to said protocol.
e.g.
//Top level protocol
protocol ApplicationState: Equatable {}
//Feature specific protocols
protocol UserState: ApplicationState {
var userDidChange: Bool { get set }
}
protocol FreezeState: ApplicationState {
var freezeState: String { get set }
}
protocol Foo {
func update(_ value: any ApplicationState)
}
//Implementation
struct Bar: Foo {
func update(_ value: any ApplicationState) { } <--------- Conformance as defined in the protocol.
func update(_ value: any UserState) { } <--------- Ideally what I would like to do since UserState also conforms to ApplicationState.
}
I am able to achieve what I want by casting the type any ApplicationState as? any UserState but I would ultimately like to remove that casting step since I would need to that in all the locations where my Foo protocol is used.
Not sure if that's exactly what I'm looking for. That doesn't seem to allow me to change to the protocol that I ideally want to use since it won't include the properties associated with UserState or FreezeState.
Here is a more detailed Bar implementation on what I wanting to do.
struct Bar: Foo {
struct State: UserState {
var userDidChange: Bool = false
}
var userState: State = State()
mutating func update(_ value: any ApplicationState) {
guard let value = value as? any UserState else { <---------- Other parts of the app would use a different protocol here, depending the needs.
return
}
userState.userDidChange = value.userDidChange
}
}
struct InternalAppState: UserState, FreezeState { <-------- Internal type that maintains state relevant to the App. It also conforms to the same protocols defined in different features.
var userDidChange: Bool = false
var freezeState: String = ""
}
//Again a very crude implementation of what I'd like to do.
let internalState = InternalState()
var bar = Bar()
bar.update(internalState) <------ Since "internalState" conforms to `ApplicationState`, I can pass it into the function, but it's opaque and the exact values I'm interested aren't readily available unless I cast as the `protocol` the receiving feature needs. I'm hoping to make this is reusable and reduce the amount of casting I need, if possible.
For more context, the protocol's will be feature specific and unaware of each other, besides the InternalState which will have access the different protocols.
The issue here is that the protocol Foo is a contract that any conforming type must uphold, specifically saying that you must be able to accept any type conforming to ApplicationState. If you want to handle a specific type, you could use an associatedtype State: ApplicationState requirement, which allows each conforming type to choose the specific type it allows to be passed. (You won’t be able to use a type like any FreezeState here, though, because it doesn’t conform to ApplicationState)
This gets me the API that I want, but the call site is where it breaks down. The type I'll be passing in is some ApplicationState and casting fails.
//Rough implementation
struct FeatureContainer<Feature: Foo> {
let feature: Foo
func receivedUpdate(_ value: some ApplicationState) {
feature.update(value as? Foo.State) <------ This casting will fail since the concrete type passed in is not concrete Foo.State,
but DOES conform to the protocol that constrained ` Foo.State` (protocol UserState).
But at this point, I don't know which `ApplicationState` protocol the `Foo.State` is using. (FreezeState or UserState).
}
}
I guessing what I want isn't possible to accomplish since the type could have multiple protocol conformances that also conform to ApplicationState.
@Quedlinbug Not a bad idea but I don't think that will work in my current app.
The core ApplicationState concrete type will be a single type that is composed of different protocols defined by the different features, i.e. the properties defined in the examples protocols FreezeState and UserState, to maintain a single source of truth. When a property on the ApplicationState concrete type changes (e.g. appState.userDidChange = true), it will post itself to the FeatureContainer, which will determines if any property defined in the properties it cares about have changed.
In classes the feature you are talking about is called "covariant return types" and "contravariant parameter types":
class SuperClass {}
class MainClass: SuperClass {}
class SubClass: MainClass {}
class Foo {
func update(_ value: MainClass) -> MainClass {
MainClass()
}
}
class Bar: Foo {
override func update(_ value: SuperClass) -> SubClass {
SubClass()
}
}
Swift protocols don't support this (could be said they require "invariant" return and parameter types. I think something like this could be considered for Swift, although it would make the language more complicated.