扩展Swift数组以按类型

时间:2015-10-13 20:33:27

标签: arrays swift generics swift-extensions

如何扩展swift数组以访问特定类型的成员?

如果数组包含从同一个超类继承的多个类的实例,则这是相关的。理想情况下,它会适当地强制执行类型检查。

一些不太有用的想法和事情:

使用filter(_:)方法可以正常工作,但确实可以强制执行类型安全。例如:

protocol MyProtocol { }
struct TypeA: MyProtocol { }
struct TypeB: MyProtocol { }

let myStructs:[MyProtocol] = [ TypeA(), TypeA(), TypeB() ]

let filteredArray = myStructs.filter({ $0 is TypeA })

filteredArray包含正确的值,但类型仍然是[MyProtocol]而不是[TypeA]。我希望用let filteredArray = myStructs.filter({ $0 is TypeA }) as! [TypeA]替换最后一个会解决这个问题,但项目会因{I} EXEC_BAD_INSTRUCTION而失败,我不明白。也许不能使用类型转换数组?

理想情况下,此行为可以包含在数组扩展中。以下内容无法编译:

extension Array {
    func objectsOfType<T:Element>(type:T.Type) -> [T] {
        return filter { $0 is T } as! [T]
    }
}

这里似乎至少有两个问题:类型约束T:Element似乎不起作用。我不确定基于泛型类型添加约束的正确方法是什么。我的意图是TElement的子类型。此外,第3行还有编译时错误,但这可能只是传播相同的错误。

4 个答案:

答案 0 :(得分:8)

SequenceTypeflatMap()方法作为&#34;可选过滤器&#34;:

extension SequenceType {
    /// Return an `Array` containing the non-nil results of mapping
    /// `transform` over `self`.
    ///
    /// - Complexity: O(*M* + *N*), where *M* is the length of `self`
    ///   and *N* is the length of the result.
    @warn_unused_result
    @rethrows public func flatMap<T>(@noescape transform: (Self.Generator.Element) throws -> T?) rethrows -> [T]
}

结合亚特的建议,使用as?代替is你 可以用它作为

let myStructs:[MyProtocol] = [ TypeA(), TypeA(), TypeB() ]
let filteredArray = myStructs.flatMap { $0 as? TypeA }

现在,filteredArray的类型被推断为[TypeA]

作为一种扩展方法,它将是

extension Array {
    func objectsOfType<T>(type:T.Type) -> [T] {
        return flatMap { $0 as? T }
    }
}

let filteredArray = myStructs.objectsOfType(TypeA.self)

注意:对于 Swift&gt; = 4.1,flatMap替换为compactMap

答案 1 :(得分:4)

而不是测试(使用is)如何投射(使用as)?

let myStructs:[MyProtocol] = [ TypeA(), TypeA(), TypeB() ]
var filteredArray = [TypeA]()
for case let t as TypeA in myStructs {filteredArray.append(t)}

答案 2 :(得分:1)

转换数组在Swift中不起作用。这是因为Swift中的数组使用泛型,就像你不能转换自定义类一样,只有类型T发生变化。 (class Custom<T>Custom<Int>() as! Custom<String>)。

您可以做的是为Array创建一个扩展方法,您可以在其中定义如下方法:

extension Array {
    func cast<TOut>() -> [TOut] {
        var result: [TOut] = []
        for item in self where item is TOut {
            result.append(item as! TOut)
        }
        return result
    }
}

答案 3 :(得分:0)

我认为规范的FP答案是使用过滤器,就像你一样,结合地图:

let filteredArray = myStructs.filter({ $0 is TypeA }).map({ $0 as! TypeA })

或者,您可以使用reduce:

let filtered2 = myStructs.reduce([TypeA]()) {
    if let item = $1 as? TypeA {
        return $0 + [item]
    } else {
        return $0
    }
}

或者,因为它改变了一个数组,因此FP友好一些:

let filtered3 = myStructs.reduce([TypeA]()) { ( var array, value )  in
    if let item = value as? TypeA {
        array.append(item)
    }
    return array
}

实际上可以缩短为FP友好的flatMap:

let filtered4 = myStructs.flatMap { $0 as? TypeA }

并将其放在扩展名中:

extension Array {
    func elementsWithType<T>() -> [T] {
        return flatMap { $0 as? T }
    }
}

let filtered5 : [TypeA] = myStructs.elementsWithType()