Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Extend Swift Array to Filter Elements by Type

How can a swift array be extended to access members of a particular type?

This is relevant if an array contains instances of multiple classes which inherit from the same superclass. Ideally it would enforce type checking appropriately.


Some thoughts and things that don't quite work:

Using the filter(_:) method works fine, but does enforce type safety. For example:

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

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

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

the filteredArray contains the correct values, but the type remains [MyProtocol] not [TypeA]. I would expect replacing the last with let filteredArray = myStructs.filter({ $0 is TypeA }) as! [TypeA] would resolve that, but the project fails with EXEC_BAD_INSTRUCTION which I do not understand. Perhaps type casting arrays is not possible?

Ideally this behavior could be wrapped up in an array extension. The following doesn't compile:

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

Here there seem to be at least two problems: the type constraint T:Element doesn't seem to work. I'm not sure what the correct way to add a constraint based on a generic type. My intention here is to say T is a subtype of Element. Additionally there are compile time errors on line 3, but this could just be the same error propagating.

like image 549
Anthony Mattox Avatar asked Nov 16 '25 04:11

Anthony Mattox


1 Answers

SequenceType has a flatMap() method which acts as an "optional filter":

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

Combined with matt's suggestion to use as? instead of is you can use it as

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

Now the type of filteredArray is inferred as [TypeA].

As an extension method it would be

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

let filteredArray = myStructs.objectsOfType(TypeA.self)

Note: For Swift >= 4.1, replace flatMap by compactMap.

like image 159
Martin R Avatar answered Nov 18 '25 21:11

Martin R