I want to store objects of different types in an array. The program below is only a minimum demo. In the anyArray:[Any] an instance of Object1 is stored. The print statement prints out the expected object type. In the following line the test of the stored object's type returns true. This means, during run time the correct object type is known and every thing seems to be fine.
class Object1 {
var name = "Object1"
}
var anyArray:[Any] = [Object1()]
print("\(type(of: anyArray[0]))")
let testResult = anyArray[0] is Object1
print("Test result:\(testResult)")
//print("Name:\((anyArray[0]).name)")
Console output:
Object1
Test result:true
However, if I try to print out the name property of the object, I get an error message from the editor:
Value of type 'Any' has no member 'name'
Well, at compile time the object's type is unknown. That's why the compiler complains. How can I tell the compiler that it is OK to access the properties of the stored object?
The difference comes from the difference from Type Checking in:
The is
operator checks at runtime whether the expression can be cast to the specified type. type(of:)
checks, at runtime, the exact type, without consideration for subclasses.
anyArray[0].name
doesn't compile since the Type Any
doesn't have a name
property.
If you're sure anyArray[0]
is an Object1
, you could use the downcast operator as!
:
print("\((anyArray[0] as! Object1).name)")
To check at runtime if an element from anyArray
could be an Object1
use optional binding, using the conditional casting operator as?
:
if let:
if let object = anyArray[0] as? Object1 {
print(object.name)
}
Or use the guard
statement, if you want to use that object in the rest of the scope:
guard let object = anyArray[0] as? Object1 else {
fatalError("The first element is not an Object1")
}
print(object.name)
If all objects in your array have a name
property, and you don't want to go through all the hoops of optional binding repeatedly, then use a protocol. Your code will look like this:
protocol Named {
var name: String {get set}
}
class Object1: Named {
var name = "Object1"
}
var anyArray:[Named] = [Object1()]
print("\(type(of: anyArray[0]))")
let testResult = anyArray[0] is Object1
print("Test result:\(testResult)")
print("Name:\(anyArray[0].name)")
Notice that anyArray
is now an array of Named
objects, and that Object1
conforms to the Named
protocol.
To learn more about protocols, have a look here.