Mark A. Donohoe
Mark A. Donohoe

Reputation: 30458

Can you check if a Type (not an instance) is a subclass of another Type?

Given this code...

class Vehicle{}

class Car : Vehicle {}

class Honda : Car {}

How would you write the function 'findFirst' below...

class TypeManager {

    var managedTypes:[Any.Type]?

    func findFirst(_ type:Any.Type) -> Any.Type? {
        return managedTypes.first{ t in t is type.Type } // <-- Doesn't like 'type'
    }
}

var typeManager = TypeManager()
typeManager.managedTypes = [
    String.self,
    Int.self,
    Honda.self
]

let firstCarType = typeManager.findFirst(Car.Type)

Note: This is actually doing a reverse-key-lookup on a dictionary. In a perfect solution, I'd first try finding an exact match on 'type', and if not found, settle for a subclass of 'type'. I just simplified the (faked) code to focus on the matching portion.

Upvotes: 3

Views: 235

Answers (2)

rollingcodes
rollingcodes

Reputation: 16072

To expand upon Martin R's great answer you can make an array extension like the following:

extension Array {
    func first<T>(ofType: T.Type) -> T.Type?  {
        return first { $0 is T.Type } as? T.Type
    }
    func first<T>(ofExactType type: T.Type) -> T.Type? {
        return first { $0 as? Any.Type == type } as? T.Type
    }
}

class Vehicle {}
class Car : Vehicle {}
class Honda: Car {}

let carTypes = [Honda.self, Vehicle.self, Car.self] // Inferred type [Vehicle]
print(carTypes.first(ofType: Car.self) ?? "n/a") // prints Honda
print(carTypes.first(ofExactType: Car.self) ?? "n/a") // prints Car

Also, just FYI, $0 as? Any.Type == type is the same as doing $0 as? Any.Type == T.self. Either one would work.

Upvotes: 1

Martin R
Martin R

Reputation: 540065

Classes are instances of a meta-type and can be checked with is and as?. You can use a generic function to pass in the sought type:

class TypeManager {

    var managedTypes:[Any.Type] = []

    func findFirst<T>(_: T.Type) -> Any.Type? {
        return managedTypes.first { $0 is T.Type }
    }
}

Example:

if let firstCarType = typeManager.findFirst(Car.self) {
    print(firstCarType) // Honda
}

Or with conditional binding and compactMap:

class TypeManager {

    var managedTypes:[Any.Type] = []

    func findFirst<T>(_: T.Type) -> T.Type? {
        return managedTypes.compactMap { $0 as? T.Type }.first
    }
}

This has the advantage that the returned type is T.Type? and not Any.Type?. (Use managedTypes.lazy.compactMap if the list can be large and short circuiting is wanted.)

Upvotes: 1

Related Questions