Hello I want to build something like below:
protocol BaseEntity: Identifiable {
var id: UUID { get set }
}
protocol DataStore {
associatedtype T: BaseEntity
typealias CompleteHandler<T> = ((Result<T, Error>) -> Void)
func read(with id: T.ID, completion: CompleteHandler<T>)
}
but in concrete implementation I want to make use of the UUID
that was defined in the BaseEntity
protocol:
class Storage<Entity: BaseEntity>: DataStore {
func read(with id: Entity.ID, completion: CompleteHandler<Entity>) {
// here, how to use that id as a UUID?
}
}
I don't know how to do it really, to define that there must be an object conforming to BaseEntity
and would like to use that UUID
id
property in the concrete implementation.
I don't want to do it like:
func read(with id: Entity.ID, completion: CompleteHandler<Entity>) {
guard let uuid = id as? UUID else {
return
}
// more code goes here...
}
How I should do it?
CodePudding user response:
Well, basically the lack of any concrete type in this implementation is what troubles the compiler to figure out the Entity.ID
type.
This can be shown by not using a generic Entity
type in your Storage
implementation, like this:
struct MyEntity: BaseEntity {
var id: UUID
}
class Storage: DataStore {
func read(with id: MyEntity.ID, completion: CompleteHandler<MyEntity>) {
print(id.uuidString) // <- `id` is of `UUID` type
}
}
Solution 1: To help the compiler understand the Entity.ID
type, you can conditionally conform to the DataStore
protocol, by stating that Entity.ID
will always be of UUID
type (which is actually what you want in your implementation):
class Storage<Entity: BaseEntity>: DataStore where Entity.ID == UUID {
func read(with id: Entity.ID, completion: CompleteHandler<Entity>) {
print(id.uuidString) // again this line passes compilation
}
}
Solution 2: Even better, you can conditional constraint your BaseEntity
protocol by stating that ID
will always be of UUID
type. That way you do not need to change your Storage
implementation:
protocol BaseEntity: Identifiable where ID == UUID {}
// or using the following which is also the same
protocol BaseEntity: Identifiable<UUID> {}
class Storage<Entity: BaseEntity>: DataStore {
func read(with id: Entity.ID, completion: CompleteHandler<Entity>) {
print(id.uuidString) // again this line passes compilation
}
}
Solution 3: Another solution, which might be more appropriate in your context, is to convert BaseEntity
to a class. That way you also do not need to change your Storage
implementation:
class BaseEntity: Identifiable {
var id: UUID
init(id: UUID) {
self.id = id
}
}
class Storage<Entity: BaseEntity>: DataStore {
func read(with id: Entity.ID, completion: CompleteHandler<Entity>) {
print(id.uuidString) // again this line passes compilation
}
}