I have the following classes:
class Shape {
var name: String
var area: Int?
init(name: String) {
self.name = name
}
}
class Rectangle : Shape {
public func calculateArea(width: Int, height: Int) {
self.area = width * height
}
}
I get a Shape
, which I decide to treat as a Rectangle
. My understanding is that I should downcast, like so:
let someShape = Shape(name: "Test shape")
let rect = someShape as! Rectangle
This fails. From Xcode:
Could not cast value of type 'Shape' (0x103c76910) to 'Rectangle' (0x103c76530)
If I instead start with a Rectangle
, upcast to a Shape
, and downcast back to Rectangle
, it works:
let rect = Rectangle(name: "Test rect")
let someShape = rect as! Shape
let downcastRect = someShape as! Rectangle
Why does my first downcast fail, but my second downcast succeed?
CodePudding user response:
The instance doesn't change when casting the type. The instance is the actual allocated object being held in memory. The variable is just a reference to the instance.
In the first example, the instance is a Shape
. The variable that references the instance can't be of type Rectangle
.
In the second example, the instance is a Rectangle
. When it's cast to a Shape
, the instance someShape
references is still a Rectangle
. That allows for someShape
to be cast back to Rectangle
.
CodePudding user response:
The reason is simple - upcasting and downcasting does not change the type of the instance, it only changes the type of the variable.
In the first case you have an instance of Shape
, which you cannot cast to a Rectangle
because not all shapes are rectangles.
In the second case you start with an instance of Rectangle
which you store to a Shape
variable. However, the instance is still a Rectangle
. Then of course you can downcast it back to Rectangle
because it's still a Rectange
. If you tried to cast to a different shape, e.g. a Triangle
, the cast would fail.
That's the whole reason why we have operators like is
and as?
. In the first case:
let someShape = Shape(name: "Test shape")
print(someShape is Rectangle) // false - this is not a Rectangle
and in the second case:
let rect = Rectangle(name: "Test rect")
let someShape = rect as! Shape
print(someShape is Rectange) // true - someShape is still a Rectangle
Also note that you have a warning for rect as! Shape
. The compiler knows that every Rectangle
is a Shape
and you can cast safely using rect as Shape
or directly write:
let someShape: Shape = rect