2

如何扩展 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]可以解决这个问题,但是项目失败了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

4 回答 4

9

SequenceType有一个flatMap()充当“可选过滤器”的方法:

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 >= 4.1,替换flatMapcompactMap.

于 2015-10-13T20:58:49.067 回答
4

而不是测试(with is)如何铸造(with as)?

let myStructs:[MyProtocol] = [ TypeA(), TypeA(), TypeB() ]
var filteredArray = [TypeA]()
for case let t as TypeA in myStructs {filteredArray.append(t)}
于 2015-10-13T20:39:45.400 回答
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
    }
}
于 2015-10-13T21:06:06.450 回答
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()
于 2015-10-13T21:10:50.823 回答