I'd like to use a protocol as an interface but I'm having trouble working it out in Swift. Can someone make sense of this error and point me in the right direction?
Maybe I'm thinking of protocols in a completely improper way.
Here is sample code:
protocol State {
associatedtype U: State
static func fromString(_: String) -> U
}
class ConcreteState1: State {
static func fromString(_ value: String) -> ConcreteState1 {
return ConcreteState1()
}
}
protocol Loader {
associatedtype T: State
func load(completion: (Result<T.U, Error>) -> Void)
}
extension Loader {
func load(completion: (Result<T.U, Error>) -> Void) {
let value = T.fromString("")
completion(.success(value))
}
}
class ConcreteState1Loader: Loader {
typealias T = ConcreteState1
}
// Abstraction to deal with various types of state
var mainState: (any State)?
// Needs to be an abstraction using the protocol because the specific loader is injected
// This is just a simplified reproduction of the issue
var loader: any Loader = ConcreteState1Loader()
// ERROR: Member 'load' cannot be used on value of type 'any Loader'; consider using a generic constraint instead
loader.load { result in
if let state = try? result.get() {
mainState = state
}
}
CodePudding user response:
the method 'load' here can not be used because the Protocol Loader is generic, it is associated with another protocol. A concrete state should be defined in a concrete loader just like what you defined in ConcreteState1Loader.
So if you should downcast the Loader
(oader as! ConcreteState1Loader).load { result in
if let state = try? result.get() {
mainState = state
}
}
CodePudding user response:
This is what I came up with. I converted the State protocol to a class and changed the static method to a class method so it could be overriden. I rely on a new GenericLoader that can convert types using those class methods.
If there is a better, more clever, way to do this, please let me know.
class State {
class func fromString(_: String) -> State {
fatalError("must be implemented in derived class")
}
}
class ConcreteState1: State {
override class func fromString(_: String) -> State {
return ConcreteState1()
}
}
protocol Loader {
func load(completion: (Result<State, Error>) -> Void)
}
class GenericLoader<T: State>: Loader {
func load(completion: (Result<State, Error>) -> Void) {
let value = T.fromString("")
completion(.success(value))
}
}
// Abstraction to deal with various types of state
var mainState: State?
// Needs to be an abstraction using the protocol because the specific loader is injected
// This is just a simplified reproduction of the issue
var loader: Loader = GenericLoader<ConcreteState1>()
loader.load { result in
if let state = try? result.get() {
mainState = state
}
}