I'm trying to use two generic protocols that relate to each other as:
protocol PersistableData {}
protocol DataStore: class {
associatedtype DataType: PersistableData
func save(data: DataType, with key: String)
func retreive(from key: String) -> DataType?
}
protocol PersistentDataModel {
// Swift infers that DataType: PersistableData as DataType == DataStoreType.DataType: PersistableData
// Setting it explicitly makes the compiler fail
associatedtype DataType
associatedtype DataStoreType: DataStore where DataStoreType.DataType == DataType
}
extension String: PersistableData {}
protocol StringDataStore: DataStore {
associatedtype DataType = String
}
class Test: PersistentDataModel {
typealias DataType = String
typealias DataStoreType = StringDataStore
}
However Xcode fails to compile saying that Type 'Test' does not conform to protocol 'PersistentDataModel'
and suggesting that Possibly intended match 'DataStoreType' (aka 'StringDataStore') does not conform to 'DataStore'
while StringDataStore
is defined as conforming to DataStore
I've read a few good resources about generic protocols including SO and this Medium post, but I could not find where the issue is.
This happening because your typealias
for associatedtype
should have concretion, not abstraction.
Thus, for your case, StringDataStore
should be a class
, not protocol
.
protocol PersistableData {}
protocol DataStore: class {
associatedtype DataType: PersistableData
func save(data: DataType, with key: String)
func retreive(from key: String) -> DataType?
}
protocol PersistentDataModel {
// Swift infers that DataType: PersistableData as DataType == DataStoreType.DataType: PersistableData
// Setting it explicitly makes the compiler fail
associatedtype DataType
associatedtype DataStoreType: DataStore where DataStoreType.DataType == DataType
}
extension String: PersistableData {}
class StringDataStore: DataStore {
typealias DataType = String
func save(data: String, with key: String) {
//
}
func retreive(from key: String) -> String? {
return nil
}
}
class Test: PersistentDataModel {
typealias DataType = String
typealias DataStoreType = StringDataStore
}
However, you can keep using protocols and solve it by using additional generics conditions in your Test
class:
class Test<T: StringDataStore>: PersistentDataModel where T.DataType == String {
typealias DataStoreType = T
typealias DataType = T.DataType
}
Using this you can tell to compiler that concrete type will be passed to Test
somewhere else.
Like this:
class ConcreteStringDataStore: StringDataStore {
func save(data: String, with key: String) {
//
}
func retreive(from key: String) -> String? {
return nil
}
}
let test = Test<ConcreteStringDataStore>()