Casting subclass from base class where subclass has generic in Swift

Viewed 682

I have a base class type and a subclass type, where the subclass includes a generic type. If I have the subclass stored in the form of the base class type but I would like to type cast it back to the subclass type, Swift won't seem to let me.

Below is an example of what I mean:

class Base {

}

class Next<T> : Base where T : UIView {
    var view: T
    init(view: T) {
        self.view = view
    }
}

let a: [Base] = [Next(view: UIImageView()), Next(view: UILabel())]
for item in a {
    if let _ = item as? Next {
        print("Hey!")
    }
}

Why is "Hey!" never printed?


EDIT:

"Hey!" is printed if the cast reads:

if let _ item as? Next<UIImageView> ...

but only for the case of the UIImageView class.

and

"Hey!" is printed if one of the items in the array a is:

Next(view: UIView())

Ideally, I would like to not know what type the generic is when casting, but I realise this may not be possible.

3 Answers

The generic Next<T> is a template of sorts that creates unique separate classes. Next<UIView> and Next<UIImageView> are two completely unrelated types.

You can unite them with a protocol:

class Base {

}

class Next<T> : Base where T : UIView {
    var view: T
    init(view: T) {
        self.view = view
    }
}

protocol NextProtocol { }

extension Next: NextProtocol { }

let a: [Base] = [Next(view: UIImageView()), Next(view: UILabel()), Base()]
for item in a {
    if item is NextProtocol {
        print("Hey!")
    } else {
        print("Only a Base")
    }
}

Output:

Hey!
Hey!
Only a Base

By defining a protocol such as NextProcotol that all classes derived from Next<T> conform to, you can refer to them as a group and distinguish them from other classes that derive from Base.

Note: To check if an item is of a type, use is instead of checking if the conditional cast as? works.

When I saw your example I was expecting it to fail during compilation.

It seems that as long as you specify a concrete base class for your T (in this case UIView), then the compiler can infer that class in as or is statements. So when you type item as? Next the compiler understands item as?Next` because UIView is a concrete type.

This would not work if instead of UIView you would use a protocol. Also, simply using Next instead of Next<UIView> (or Next<concrete subclass of UIView>) will result in a compiler error outside an is or as statement.

This is where protocols comes to help.

you need a dummy protocol to confirm Next to it

And use that dummy protocol to check your items types.

Therefore we create a dummy protocol confirm Next to it and use that protocol to compare items.

code would be something like this.

class Base {

}

class Next<T> : Base where T : UIView {
    var view: T
    init(view: T) {
        self.view = view
    }
}
protocol  MyType  {

}

extension Next: MyType {}

let a: [Base] = [Next(view: UILabel()), Next(view: UILabel())]
for item in a {
    if let _ = item as? MyType {
        print("Hey!")
    }
}

UIView is super class for all the UIElements.

saying this will result into true,

if let _ = UILabel() as? UIView {print("yes") }

Next does not confirm to UIView but rather it requires something that confirm to UIView therefore you can't use the subs of UIView to check if they are Next, next have no exact type,

here we use the dummy protocol above !

Related