binloan
binloan

Reputation: 313

Swift generics and subclasses

my question might be simple, but it got me puzzled a bit: Imagine I have an array of different objects which all have a common parent class "MyClass".

var values = [MyClass]()

However they all have their specific subclass like for example "MyClassSubclass".

values.append(MyClassSubclass())

I now want to create a generic method returning me the first object inside this array which is of type MyClassSubclass. I would like to prevent casting the object, but instead have a generic method which takes the subclass object as T parameter and returns me the first occurrence of subclass T inside this array.

I thought of something like this (but surely that does not work):

func getFirst<T: MyClass>(_ ofType : T.Type) -> T?

I guess I'm just stuck and I don't know what to search for, so if someone could help me I would greatly appreciate it.

Edit Example based on the above values:

class MyClass {}
class MyClassSubclass : MyClass {}
class MyClassSubclass2 : MyClass{}

var values = [MyClass]()
values.append(MyClassSubclass())
values.append(MyClassSubclass2())

//Return the first class element appearing here as a subclass type
func getFirst<T>(_ ofType : T.Type) -> T?{}

Thanks

Upvotes: 3

Views: 813

Answers (3)

Martin R
Martin R

Reputation: 539685

One approach is to iterate over the array and use optional binding to check if an element is of the given type:

func getFirst<T: MyClass>(ofType: T.Type) -> T? {
    for elem in values {
        if let item = elem as? T {
            return item
        }
    }
    return nil
}

This can be simplified using for case with the as pattern:

func getFirst<T: MyClass>(ofType: T.Type) -> T? {
    for case let item as T in values {
        return item
    }
    return nil
}

Another approach is to use flatMap to find all items of the given type and return the first one:

func getFirst<T: MyClass>(ofType: T.Type) -> T? {
    return values.flatMap { $0 as? T }.first
}

If the array can be large and you want to avoid the creation of an intermediate array then you can use lazy:

func getFirst<T: MyClass>(ofType: T.Type) -> T? {
    return values.lazy.flatMap { $0 as? T }.first
}

As an array extension method this would be

extension Array {
    func getFirst<T>(ofType: T.Type) -> T? {
        return flatMap { $0 as? T }.first
    }
}

Upvotes: 7

思齐省身躬行
思齐省身躬行

Reputation: 181

If you'd like to use global function, which is not recommended, try this

func getFirst<T>(ofType: T.Type) -> T? where T: MyClass {
  for value in values where value is T {
    return value as? T
  }
  return nil
}

let first = getFirst(ofType: MyClassSubclass2.self)

The first answer should be better for Swift.

Upvotes: 2

xoudini
xoudini

Reputation: 7031

This feels a bit like abuse of generics, but here's an extension:

extension Array where Element == MyClass {
    func getFirst<T>(_ ofType: T.Type) -> T? {
        return self.first(where: { ofType == type(of: $0) }) as? T
    }
}

The method can then be called as let first = values.getFirst(MyClassSubclass.self).

I'd personally prefer simply casting inline for clarity:

let first = values.first(where: { type(of: $0) == MyClassSubclass.self }) as? MyClassSubclass

Upvotes: 1

Related Questions