Kaktusiarz Kaktusiarz - 4 months ago 15
Swift Question

Creating collection of different enums

Is it possible to create array of different enum types?

E.g.:

MyEnums.swift:

class MyEnums {
enum FirstEnum: String {
case Type1, Type2, Type3
}

enum SecondEnum: String {
case Type1, Type2, Type3
}
}


IteratorManager.swift:

class IteratorManager {
var enumsArray = [MyEnums]()

func iterate() {
for e in enumsArray {
print(e.rawValue)
}
}
}


I tried many solutions - extensions, protocols, grouping in class (like in the example above) in struct or enum - but nothing works.

Answer

Yes it is but you should be aware of how you do it.

Enums create a concrete Type. Swift is Type Safe language. An array of FirstEnum is not permitted to contain items that are not of type FirstEnum.

However, there is are 2 ways to achieve this. 1. Protocols to the Resuce 2. Type Lie

I will go with both approaches but i will never recommend using the 2nd option although it seems to be simple and eluding.

I will start with the second one.

enum FirstEnum: String {
    case Type1, Type2, Type3
}

enum SecondEnum: String {
    case Type1, Type2, Type3
}

Lets create some concrete types of enums.

let firstEnum1 = FirstEnum.Type1
let firstEnum2 = FirstEnum.Type2
let secondEnum1 = SecondEnum.Type3

Lets pack them using 2nd option:

let a:[Any] = [firstEnum1, firstEnum2, secondEnum1] //works

Lets see how we can extract it later on:

for index in a {
 if let a = index as? FirstEnum {  //This is bad
    print(a.rawValue)
 }

 if let a = index as? SecondEnum { //Aweful
    print(a.rawValue)
 }
}

See we have to cast it back to what we inserted. We have to use expectation and who knows what we get back might be a String instead of Enum. Because Any can take literally Any.

Let see how we can solve this issue by 1st option:

let b:[RawRepresentable] = [firstEnum1, secondEnum1]  //Cant do so because of Associated Type

That piece of code does not work because it RawRepresentable is a incomplete type. So we need to define our own protocol.

protocol CustomStringRepresentableEnum {
   var rawValue: String { get }
}

Then of course our Enums support rawValue so we can retroactively model this protocol.

 extension FirstEnum: CustomStringRepresentableEnum { }
 extension SecondEnum: CustomStringRepresentableEnum { }

Now we can create a array of concrete known type.

let b:[CustomStringRepresentableEnum] = [firstEnum1, firstEnum2, secondEnum1]

And this is good because we are not allowed to enter anything besides the thing that conforms to the protocol. This is properly Typed and constrained and compiler will help you a lot to prevent bad things from happening like wrong casting.

Lets use it to prove we wrote some good code.

for index in b {
    print(index.rawValue)
}

So there it is. You can have different enums in one array but somehow they need to be homogeneous. You can cheat the compiler with Any and write ugly code or use Protocols and Generics to write effective and robust code. Choice is yours.