Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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 its 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))")

Which produce the following output:

Dynamic Type == __NSArrayI
ouput : NSArray
Dynamic Type == Swift.Array<Swift.String>
ouput : NSArray
Dynamic Type == Swift.Array<Swift.String>
ouput : NSArray
Dynamic Type == Swift.Array<Swift.Int>
ouput : NSArray
Dynamic Type == Swift.Array<__lldb_expr_37.myStruct>
ouput : Default

I am wondering why the case as [Any] doesn't get 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)

like image 609
Vincent Bernier Avatar asked Jul 22 '26 09:07

Vincent Bernier


2 Answers

To decide the most reliably whether a variable is any kind of array is to use reflection, in Swift 1.2:

let array = []
let mirror = reflect(array)
let isArray = mirror.disposition == MirrorDisposition.IndexContainer

and in Swift 2.0:

let anArray = []
let mirror = Mirror(reflecting: anArray)
let isArray = mirror.displayStyle == .Collection

And just for curiosity, it is interesting to check out these enums:

enum MirrorDisposition { //Swift 1.2
    case Struct
    case Class
    case Enum
    case Tuple
    case Aggregate
    case IndexContainer
    case KeyContainer
    case MembershipContainer
    case Container
    case Optional
    case ObjCObject
}

enum DisplayStyle { //Swift 2.0
    case Struct
    case Class
    case Enum
    case Tuple
    case Optional
    case Collection
    case Dictionary
    case Set
}

UPDATED Here is a full pattern match example:

func switchOnAny(any: Any) -> String {
    println("Dynamic Type == \(any.dynamicType)")
    switch any {
    case let array as Any where reflect(any).disposition == MirrorDisposition.IndexContainer:
        return "Any kind of array"
    default:
        return "Default"
    }
}
like image 79
Dániel Nagy Avatar answered Jul 24 '26 23:07

Dániel Nagy


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"
}
like image 39
Qbyte Avatar answered Jul 25 '26 00:07

Qbyte



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!