Can't perform methods of objects stored in Array[Any]

71 views Asked by At

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?

2

There are 2 answers

5
ielyamani On BEST ANSWER

The difference comes from the difference from Type Checking in:

  • runtime, or
  • compile time

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.

0
Rakesha Shastri On

You object is still of type Any. You just checked if it can be of type Object1, but you did not cast it. If you want the object as Object1, you need to cast it.

Also if multiple classes can have name, you need to use Protocol like @vadian has mentioned in his comment and cast it to that protocol.

protocol NameProtocol {
    var name: String {get set}
}

class Object1: NameProtocol {
    var name = "Object1"
}

if let testResult = anyArray[0] as? NameProtocol {
     print(testResult.name)
}

Edit: "I want to store objects of different types in an array". The solution that you have marked as correct will not work if all the objects that you have do not conform to the protocol.