Search code examples
swiftdowncast

Why downcast array item in Swift?


Why must I downcast an array item in Swift, if the sub-class of the array item is known?

 > class B {let t: String = "-B-"}
 > class B1:B {let t1: String = "-B1-"}
 > class B2:B {let t2: String = "-B2-"}
 > let bunch = [B(), B1(), B2()]

As expected:

 > print(type(of:bunch))
 Array<B>

The sub-class of each element of the array is known:

 > for item in bunch {print(type(of:item))}
 B
 B1
 B2

Yet (as documented) we cannot access the item's sub-class member, and we have to down-class:

> if bunch[1] is B1 {let b1 = bunch[1] as! B1; print(b1.t1)} 
-B1-

because this does not work:

> bunch[1].t1
error: repl.swift:17:6: error: value of type 'B' has no member 't1'; did you mean 't'?

Why can Swift determine the sub-class using type(of:) but it cannot infer it when accessing a member in that sub-class? Is it a bug, a historic hang-over, or am I missing something?


Solution

  • Swift arrays are collections of items of a single type. The type of the array

    let bunch = [B(), B1(), B2()]
    

    is inferred as [B] because B is the “nearest common superclass” of the given three elements. Consequently, bunch[1] has the type B and not B1. That is why bunch[1].t1 does not compile.

    Of course you can print the actual type of an array element at runtime with type(of:), or check it against B1. The latter is better done with optional binding:

    if let b1 = bunch[1] as? B1 {
        print(b1.t1)
    }