Swift 阵列上的模式匹配<Any>

Swift Pattern match on Array<Any>

Swift 1.2
我正在尝试在一个函数中的 switch case 中进行模式匹配,该函数采用 Any 类型作为参数,以便分派给私有的更专业的 init。

这是一个 Playground 推断:

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

产生以下输出:

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

我想知道为什么 as [Any] 没有得到它,因为我从不请求 NSArray?

我是否可以假设任何类型的 Swift 数组都会出现在 NSArray 案例中,或者我是否需要编写 2 个案例语句(一个用于 NSArray,一个用于 [Any])以涵盖我的回来(貌似会有需求)?


在进行更多测试后,我发现当我提供一个自定义结构数组时,none 模式将匹配。我将需要像 [myStruct] 这样的匹配才能识别。这正是我要避免的,因为它只是我可以接受的选项之一。


为了提供更多背景信息,我将我的项目放在 Github : https://github.com/VinceBurn/SwiftyPlist/tree/test/init-Any 上。 该项目是关于 TDD 并将 属性 列表表示为可以通过下标访问的类似树的结构。 (比如 SwiftyJSON)

不幸的是,Array 之类的泛型类型之间的转换还没有得到完全支持(目前)。即使想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]'

不使用协议也没有好的解决方法。如果你真的想要这种动态行为,你可以将反射与 reflect() 函数结合使用。在 Swift 2 中它们更强大,但仍然不是一个好的解决方案。

编辑:

一个解决方案,其协议通过扩展被所有人 Arrays 采用(仅适用于您的特定情况):

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

extension Array: ArrayType {
    var anyValues: [Any] {
        return self.map { [=11=] 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"
}

最可靠地判断一个变量是否是任何类型的数组是使用反射,在Swift 1.2:

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

Swift 2.0:

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

出于好奇,查看这些枚举很有趣:

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
}

已更新 这是一个完整的模式匹配示例:

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"
    }
}

我建议使用自定义协议扩展 Array 类型,您可以像这样检查它:

protocol ArrayType {}
extension Array : ArrayType {}

那么你可以这样做:

let array : Any = [1, 2, 3, 4]
array is ArrayType    // true

也可以看看我的另一个回答

但实际上看起来您不想拥有一个以 Any 作为参数的 public 初始值设定项(您很少想要它),而是两个不同的初始值设定项,一种用于数组,一种用于非数组,如下所示:

class MyClass {
    init<T>(array: [T]) {

    }

    init<T>(nonArray: T) {

    }
}

结论:在Swift 1.2

通过 Kametrixom 和 Daniel Nagy 的回答,可以为所有类型的数组输入一个开关盒。
但是在案例中,我无法将项目转换为适用于所有案例的可用数组。

所以最后我留下了 2 个案例陈述,一个

case let array as NSArray:
    return "NSArray"
case let array as [myStruct]:
    return "myStruct array"