获取未知类型数组的元素和计数

Get elements and count of Array of unknown type

假设我们有一个数组,分配给类型为 Any

的变量
let something: Any = ["one", "two", "three"]

我们还假设我们不知道它是数组还是完全不同的东西。而且我们也不知道我们正在处理的是哪种 Array.Element

现在我们想知道它是否是一个数组。

let isArray = something is Array // compiler error
let isArray = (something as? [Any?] != nil) // does not work (array is [String] and not [Any?])

是否有任何优雅的解决方案可以从 swift 类型系统中提取以下信息:

  1. 给定对象是否为数组
  2. 数组的个数是多少
  3. 给我数组的元素

(桥接到 NSArray 对我来说不是解决方案,因为我的数组也可以是 [Any?] 类型并包含 nil 值)

我想到的唯一解决方案如下,但我不知道它是否是最优雅的:)

protocol AnyOptional {
    var anyOptionalValue: Optional<Any> { get }
}
extension Optional: AnyOptional {
    var anyOptionalValue: Optional<Any> {
        return self
    }
}
protocol AnyArray {
    var count: Int { get }
    var allElementsAsOptional: [Any?] { get }
}
extension Array: AnyArray {
    var allElementsAsOptional: [Any?] {
        return self.map {
            if let optional = [=10=] as? AnyOptional {
                return optional.anyOptionalValue
            }
            return [=10=] as Any?
        }
    }
}

现在你可以说

if let array = something as? AnyArray {
    print(array.count)
    print(array.allElementsAsOptional)
}

我喜欢@stefreak 的问题和他的解决方案。牢记@dfri 关于 Swift 运行时自省的出色回答,但是,我们可以在某种程度上简化和概括@stefreak 的 "type tagging" 方法:

protocol AnySequenceType {
    var anyElements: [Any?] { get }
}

extension AnySequenceType where Self : SequenceType {
    var anyElements: [Any?] {
        return map{
            [=10=] is NilLiteralConvertible ? Mirror(reflecting: [=10=]).children.first?.value : [=10=]
        }
    }
}

extension Array : AnySequenceType {}
extension Set   : AnySequenceType {}
//    ... Dictionary, etc.

使用:

let things:  Any = [1, 2]
let maybies: Any = [1, nil] as [Int?]

(things  as? AnySequenceType)?.anyElements // [{Some 1}, {Some 2}]
(maybies as? AnySequenceType)?.anyElements // [{Some 1}, nil]

请参阅 Swift Evolution mailing list discussion 了解允许协议扩展的可能性:

extension<T> Sequence where Element == T?

然而,在目前的实践中,更常见且有点虎头蛇尾的解决方案是:

things as? AnyObject as? [AnyObject] // [1, 2]

// ... which at present (Swift 2.2) passes through `NSArray`, i.e. as if we:

import Foundation
things as? NSArray  // [1, 2]

// ... which is also why this fails for `mabyies`
maybies as? NSArray // nil

无论如何,这一切让我明白的是,一旦你丢失了类型信息,就再也回不去了。即使您反思 Mirror ,您仍然会得到一个 dynamicType ,您必须将其切换到预期的类型,以便您可以转换该值并按原样使用它......所有在运行时,所有永远在编译时检查和完整性之外。

这对我来说适用于游乐场:

// Generate fake data of random stuff
let array: [Any?] = ["one", "two", "three", nil, 1]
// Cast to Any to simulate unknown object received
let something: Any = array as Any

// Use if let to see if we can cast that object into an array
if let newArray = something as? [Any?] {
    // You now know that newArray is your received object cast as an
    // array and can get the count or the elements
} else {
    // Your object is not an array, handle however you need.
}

我发现转换为 AnyObject 适用于对象数组。仍在研究值类型的解决方案。

let something: Any = ["one", "two", "three"]

if let aThing = something as? [Any] {
    print(aThing.dynamicType) // doesn't enter
}

if let aThing = something as? AnyObject {
    if let theThing = aThing as? [AnyObject] {
        print(theThing.dynamicType) // Array<AnyObject>
    }
}

作为@milos 和OP:s 协议一致性检查的替代方法,我将添加一个使用something 运行时内省的方法(示例中的foobar下面)。

/* returns an array if argument is an array, otherwise, nil */
func getAsCleanArray(something: Any) -> [Any]? {
    let mirr = Mirror(reflecting: something)
    var somethingAsArray : [Any] = []
    guard let disp = mirr.displayStyle where disp == .Collection else {
        return nil // not array
    }

    /* OK, is array: add element into a mutable that
     the compiler actually treats as an array */
    for (_, val) in Mirror(reflecting: something).children {
        somethingAsArray.append(val)
    }

    return somethingAsArray
}

用法示例:

/* example usage */
let foo: Any = ["one", 2, "three"]
let bar: [Any?] = ["one", 2, "three", nil, "five"]

if let foobar = getAsCleanArray(foo) {
    print("Count: \(foobar.count)\n--------")
    foobar.forEach { print([=11=]) }
} /* Count: 3
     --------
     one
     2
     three      */

if let foobar = getAsCleanArray(bar) {
    print("Count: \(foobar.count)\n-------------")
    foobar.forEach { print([=11=]) }
} /* Count: 5
     -------------
     Optional("one")
     Optional(2)
     Optional("three")
     nil
     Optional("five")  */