Reputation: 1569
I have two distinct types that represent the same data, and have the exact same "shape". The two distinct types are code-gen'd and I am forced to deal with them. But, I want to make them conform to a common protocol so that I can treat both types the same. Here's an example:
Let's say that these are my two code-gen'd types, which I am stuck with:
struct User1 {
var email: String
var name: Name
struct Name {
var givenName: String
var familyName: String
}
}
struct User2 {
var email: String
var name: Name
struct Name {
var givenName: String
var familyName: String
}
}
I want to be able to use these types interchangeably, so I create a couple protocols that they can conform to:
protocol NameRepresenting {
var givenName: String { get }
var familyName: String { get }
}
protocol UserRepresenting {
var email: String { get }
var name: NameRepresenting { get }
}
And then I attempt to make them conform:
extension User1.Name: NameRepresenting {}
// Error: Type 'User1' does not conform to protocol 'UserRepresenting'
extension User1: UserRepresenting {}
extension User2.Name: NameRepresenting {}
// Error: Type 'User2' does not conform to protocol 'UserRepresenting'
extension User2: UserRepresenting {}
I expect the above to work, but compilation fails with the errors commented above. Is there any elegant way to get these two types to conform to a common protocol so I can use them interchangeably?
Upvotes: 3
Views: 1063
Reputation: 271355
The name
properties of the generated structs have type Name
, not NameRepresenting
as required by the protocol. Covariant returns are not supported in Swift just yet :(
What you can do is to add an associated type requirement:
protocol UserRepresenting {
associatedtype Name : NameRepresenting
var email: String { get }
var name: Name { get }
}
This requires that the conformers to have a type that conforms to NameRepresenting
and is the type of the name
property.
However, now that it has an associated type requirement, you cannot use UserRepresenting
as the type of a variable/function parameter. You can only use it in generic constraints. So if you have a function that takes a UserRepresenting
, you need to write it like this:
func someFunction<UserType: UserRepresenting>(user: UserType) {
}
and if one of your classes/structs need to store a property of type UserRepresenting
, you need to make your class/struct generic too:
class Foo<UserType: UserRepresenting> {
var someUser: UserType?
}
This may or may not work for your situation. If it doesn't, you can write a type eraser:
struct AnyUserRepresenting : UserRepresenting {
var email: String
var name: Name
struct Name : NameRepresenting {
var givenName: String
var familyName: String
}
init<UserType: UserRepresenting>(_ userRepresenting: UserType) {
self.name = Name(
givenName: userRepresenting.name.givenName,
familyName: userRepresenting.name.familyName)
self.email = userRepresenting.email
}
}
Now you can convert any UserRepresenting
to this AnyUserRepresenting
, and work with AnyUserRepresenting
instead.
Upvotes: 3