Vincent Bernier Vincent Bernier - 9 days ago 6
Swift Question

Swift Pattern match on Array<Any>

Swift 1.2

I'm trying to pattern match in a switch case in a function that take a type Any as it's parameter, in order to dispatch to a private more specialize init.

Here is a Playground extrapolation :

import Foundation

struct myStruct {
}

func switchOnAny(any: Any) -> String {
println("Dynamic Type == \(any.dynamicType)")
switch any {
case let array as [Any]:
return "Array"
case let array as NSArray:
return "NSArray"
default:
return "Default"
}
}

let emptyStringArray : [String] = []
let stringArray : [String] = ["Bob", "Roger"]
let intArray = [1, 2, 3]
let customStructArray : [myStruct] = []

println("\t\touput : \(switchOnAny([]))")
println("\t\touput : \(switchOnAny(emptyStringArray))")
println("\t\touput : \(switchOnAny(stringArray))")
println("\t\touput : \(switchOnAny(intArray))")
println("\t\touput : \(switchOnAny(customStructArray))")


Wich produce the following output :


Dynamic Type == __NSArrayI

ouput : NSArray

Dynamic Type == Swift.Array

ouput : NSArray

Dynamic Type == Swift.Array

ouput : NSArray

Dynamic Type == Swift.Array

ouput : NSArray

Dynamic Type == Swift.Array<__lldb_expr_37.myStruct>

ouput : Default


I am wondering why the case
as [Any]
don't gets it since I'm never requesting an NSArray?

And can I assume that any kind of Swift array will get in the NSArray case or will I need to write 2 case statement (one for NSArray and one for [Any]) in order to cover my back (apparently there will be a need)?




After making some more test, I can see that when I'm providing an array of a custom struct none of the pattern will match. I will need to have a match like [myStruct] for it to recognize. Which is exactly what I'm trying to avoid, because it is only one of the option that I can receive.




To give more context I've put my project on Github : https://github.com/VinceBurn/SwiftyPlist/tree/test/init-Any.
The project is about TDD and representing a Property list as a Struct like tree that can be accessed by subscript. (like SwiftyJSON)

Answer

Unfortunately casting between generic types like Array is not fully supported (yet). There are also odd situations even if you want to upcast:

let emptyStringArray : [String] = []
emptyStringArray as [Any]    // succeeds

let stringArray : [String] = ["Bob", "Roger"]
stringArray as [Any]         // error! due to the elements?!

let intArray = [1, 2, 3]
intArray as [Any]            // error

let customStructArray : [myStruct] = []
customStructArray as [Any]   // '[myStruct]' is not convertible to '[Any]'

There is also no good workaround without using a protocol. If you really want to have this dynamic behavior you could use reflections with the reflect() function. In Swift 2 they are more powerful, but it is still not a good solution.

Edit:

A solution with a protocol which gets adopted by all Arrays through an extension (only for your specific case):

protocol ArrayType {
    var anyValues: [Any] { get }
}

extension Array: ArrayType {
    var anyValues: [Any] {
        return self.map { $0 as Any }
    }
}

// now the switch gets rewritten as
switch any {
case let array as ArrayType:
    let anyArray = array.anyValues
    return "Array"
case let array as NSArray:
    return "NSArray"
default:
    return "Default"
}
Comments