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)
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"
}
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"
}
}
I suggest extending the Array
type with a custom protocol that you can use to check it like so:
protocol ArrayType {}
extension Array : ArrayType {}
Then you can do something like this:
let array : Any = [1, 2, 3, 4]
array is ArrayType // true
Also have a look at my other answer here.
But it actually looks like you don't want to have a single public initializer which takes Any
as an argument (You very rarely want that at all), but rather two different initializers, one for arrays, one for non-arrays like so:
class MyClass {
init<T>(array: [T]) {
}
init<T>(nonArray: T) {
}
}
Conclusion : in Swift 1.2
with the Kametrixom and Daniel Nagy answer, it is possible to enter a single switch case for all sort of Array.
But inside the case, I've not been able to cast the item to a usable array for all case.
So in conclusion I'm left with 2 case statement, one
case let array as NSArray:
return "NSArray"
case let array as [myStruct]:
return "myStruct array"
来源:https://stackoverflow.com/questions/32156910/swift-pattern-match-on-arrayany