Reputation: 8664
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)
Upvotes: 3
Views: 2293
Reputation: 13243
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"
}
Upvotes: 2
Reputation: 8664
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"
Upvotes: 0
Reputation: 12015
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"
}
}
Upvotes: 3
Reputation: 14973
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) {
}
}
Upvotes: 1