3

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

3 Answers 3

7

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
    }
}
Sign up to request clarification or add additional context in comments.

5 Comments

I like your approach. Which one you think would be the fastest to return the first occurrence?
@binloan: My guess would be that the for-loops are the fastest, one would have to to measure and compare. But I would recommend to check first if the performance of this part is crucial to your app. If the arrays are not large then I doubt that there will be much difference.
Okay I will test that. My arrays currently are not large, but this might happen in the future and I would like to have a proper implementation.
+1 for the comprehensive answer. And @binloan, the for loops would probably be the faster (on average), since the flatMaps don't short circuit the operation.
@xoudini: lazy.flatMap returns a "lazy collection", so that would short-circuit, but it has more overhead. So it depends on the array size.
2

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.

2 Comments

This looks rather smooth even though the "?" cast will be slower than if you used the "if let " structure. But why in your opinion this function is not recommended?
mainly because it's not elegant for reusing , the "extension array" is better
1

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

2 Comments

I like the extension way however to clarify this. It will be part of a touch framework where a framework user shall be able to easily retrieve the first instance of an object type. That's why I want this. So extensions would be bad because of the needed public scope. Also your second approach would not work because it moves the load to the framework user (am I right?) -
Why would extensions be bad in a framework? A lot of frameworks use extensions of built-in Swift and UIKit types.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.