Search code examples
iosswiftxcodeuikituicolor

UIColor-subclass crashes when casting from Any?


I know, subclassing UIColor isn't recommended. Apple says

Most developers have no need to subclass UIColor

But I do. More on why can be found from another question I posted yesterday. That particular issue was solved, but I met another problem.

Let's say I have this custom color class:

class MyColor:UIColor{
    convenience init(test:String){
        self.init(red: 0, green: 0, blue: 0, alpha: 1)
    }
}

//Then do this anywhere:
let myColor = MyColor(test: "test")
let temp:Any? = myColor
let c = temp as! MyColor

This crashes. It crashes because it can't cast temp to MyColor:

Could not cast value of type 'UIDeviceRGBColor' (0x..) to 'MyColor' (0x..)

myColor is an instance of MyColor. This same instance is stored in a variable of type Any?, and then cast back to MyColor. But it can't.

Though, if I cast it to UIColor everything works. But I can't do that in my case (explained in the previous question).

Why doesn't this work?


Solution

  • The problem is that UIColor is implemented as a so-called class cluster. It's a kind of class factory, but the factory is working implicitly under the hood. In your example, if you mean to create a MyColor instance, what happens internally is the following:

    • MyColor.init calls the initializer of the super class
    • The super class then delegates to a internal class factory and changes the concrete implementation from MyColor into something adequate to the parameters, in your case UIDeviceRGBColor.
    • This means, UIColor.init does return a different instance than the one you intended to create. This is a subclass of UIColor, but not of MyColor any more.

    In Objective C, you can trace this behaviour more easily:

    UIColor *color = [UIColor alloc];
    NSLog(@"Address after alloc: %p - class: %@", color, [color class]);
    color = [color initWithRed:1.0, green:1.0, blue:1.0, alpha:1.0];
    NSLog(@"Address after init:  %p - class: %@", color, [color class]);
    

    You should get a different address and class after the initalizer has been called.