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.
Is this possible?
ksluder
(Kyle Sluder)
2
You would typically use protocol-based dispatch for this for this:
protocol ApplicationState {
func beUpdatedBy(_ foo: Foo)
}
extension Foo {
func update(_ value: ApplicationState) {
value.beUpdatedBy(self)
}
}
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.
j-f1
(Jed Fox)
4
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)
2 Likes
Yep, I get that. I explored using an associatedtype in the protocol as well and what I'm attempting to do also didn't work.
protocol Foo {
associatedtype: State: ApplicationState
func update(_ value: State)
}
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.
Do constraints solve the problem?
extension Foo {
mutating func update(_ value: some UserState) where State: UserState { }
mutating func update(_ value: some FreezeState) where State: FreezeState { }
}
extension FeatureContainer {
mutating func receivedUpdate(_ value: some UserState) where Feature.State: UserState {
feature.update(value)
}
mutating func receivedUpdate(_ value: some FreezeState) where Feature.State: FreezeState {
feature.update(value)
}
}
@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.
tera
8
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.