How to filter objects of certain type from [AnyObject] array

1.1k Views Asked by At

Is it possible to filter an array of [AnyObject] to yield all elements of a given type, and none other?

I can do it if the type is known at compile time:

class MyClass1: CustomStringConvertible {

    var value: Int

    var description: String {
        return "MyClass1: \(value)"
    }

    init(_ value: Int) {
        self.value = value
    }
}

class MyClass2: CustomStringConvertible {

    var value: Int

    var description: String {
        return "MyClass1: \(value)"
    }

    init(_ value: Int) {
        self.value = value
    }
}

class MySubClass1: MyClass1 {
    override var description: String {
        return "MySubClass1: \(value)"
    }
}

let a1 = MySubClass1(1)
let a2 = MySubClass1(2)
let b1 = MyClass1(3)
let b2 = MyClass2(4)

let array: [AnyObject] = [a1, b1, a2, b2]

func getClass1ObjectsFromArray(_ array: [AnyObject]) -> [MyClass1] {
    return array.compactMap( { $0 as? MyClass1 })
}

func getSubClass1ObjectsFromArray(_ array: [AnyObject]) -> [MySubClass1] {
    return array.compactMap( { $0 as? MySubClass1 })
}

print(getClass1ObjectsFromArray(array))

print(getSubClass1ObjectsFromArray(array))

Prints:

[MySubClass1: 1, MyClass1: 3, MySubClass1: 2]
[MySubClass1: 1, MySubClass1: 2]

For every type I want to filter on, I had to write a separate function. This looks ugly to me, and will not work when the type to be selected for is only known at run time.

Question:

Is there a generic way to write such a function? Preferably something like:

func getObjectsOfType(_ type: TypeExpression, fromArray array: [AnyObject])
 -> [TypeExpression] {
     ... 
    }

Or any other way to achieve this?

Thanks for any help!

1

There are 1 best solutions below

5
Fogmeister On

I think you could use something like this...

let filteredArray = array.compactMap { $0 as? RequiredType }

This will filter the array and return a typed array containing only the type you want.

Caveat

Having said that. In Swift you should be avoiding heterogeneous arrays where possible. Arrays should really only contain one type of item.

A bit of code testing...

Tested in Playground...

let array: [Any] = [1, "hello", 3, 3.1415, "world"]

let filteredArray = array.compactMap { $0 as? String }

filteredArray

Output:

filteredArray = ["hello", "world"]

Edit 1

You could also create a generic function something like this...

func filter<T>(array: [Any]) -> [T] {
    return array.compactMap { $0 as? T }
}

let filteredArray: [String] = filter(array: array)

This will then filter based on the type of the output array that you want.

I'm not sure what you mean by only knowing the type you want at run time. Can you give a more concrete example of what you mean?

Edit 2

Another possibility is a generic function like this...

func filter<T>(array: [Any], byType typeObject: T) -> [T] {
    return array.compactMap { $0 as? T }
}

let filteredArray = filter(array: array, byType: "some string")

This uses the type information of the second parameter to filter the array by that type of item.

Edit 3

If you don't like passing in an instance of the type then you can pass the type itself...

func filter<T>(array: [Any], byType typeObject: T.Type) -> [T] {
    return array.compactMap { $0 as? T }
}

let filteredArray = filter(array: array, byType: String.self)

But I'm not sure what more you're getting from this than just filtering by string in the first place?