Hi, I have a few types which conform to a protocol and I'd like to put values of those types in a single array. Because the protocol inherits from Equtable
and Swift doesn't support generalized existential, it's impossible to define an array of that protocol type. After researching on the net, I find the two typical solutions are using type eraser or enum wrapper.
Type eraser is not applicable in my case, because I'd like to keep the original values, which are not saved at any other places. From my understanding, if I convert the original values to type erased values and save the latter in the array, there would be no way to get back the original values.
So I tried the enum wrapper approach. It works, but it can easily lead to boilerplate code. See the following code.
1 protocol Foo: Equatable {
2 var foo: Int { get }
3 }
4 struct A: Foo {
5 var foo: Int
6 var a: Int
7 }
8 struct B: Foo {
9 var foo: Int
10 var b: Int
11 }
12 enum FooWrapper: Foo {
13 case a(_ value: A)
14 case b(_ value: B)
15 var foo: Int {
16 switch self {
17 case .a(let value):
18 return value.foo
19 case .b(let value):
20 return value.foo
21 }
22 }
23 }
24 var store: [FooWrapper]
The issue is, if protocol Foo
has, say, ten properties. I need to repeat line 16-21 ten times, one for each property. I wonder if there is a better way to deal with it?
I tried a few approaches, but none of them worked. One of the most promising approach (in my opinion) is like the following:
enum FooWrapper: Foo {
case a(_ value: A)
case b(_ value: B)
var foo: Int {
value.foo
}
var value: Foo { // Error!
switch self {
case .a(let value):
return value
case .b(let value):
return value
}
}
}
It fails to compile due to the same reason that we can't use Foo
as array type. It doesn't work either if I change the return type to some Foo
, because the value
property doesn't return a fixed concrete type.
I also considered using dynamic member lookup feature, but find they're not helpful in this case.
I think there is one way that might work. It's to let the value
property return a type erased value, like the following:
enum FooWrapper: Foo {
case a(_ value: A)
case b(_ value: B)
var foo: Int {
value.foo
}
var value: AnyFoo {
switch self {
case .a(let value):
return AnyFoo(value)
case .b(let value):
return AnyFoo(value)
}
}
}
I don't like this approach, however, because it creates a type erased value every time the value is accessed. These values are the main entities in my app and they are accessed very frequently. Although it's unlikely to cause any real performance issue, it doesn't seem good to me (please correct me if I'm wrong).
From what I read on the net, enum wrapper is a frequently used approach for heterogeneous array in Swift. I wonder how you guys do it? Thanks.