How to init my subclass with an instance of its superclass?

2k views Asked by At

In my app I read calendar events of type EKEvent, and I've made an extension with a lot of computed vars so I can easily get the duration, number of man-hours etc. for each event in the calendar. But in large scale, the performance is bad - so I want to use lazy vars instead, to cache all my extra data.

Therefore, I want to make a subclass of EKEvent - called CustomEvent, which adds the lazy vars, but my problem is that the EKEventStore always returns EKEvents, and I need to convert that to instances of my CustomEvent subclass, in order to be able to access the lazy vars etc.

A simple typecast is not enough, and I've tried in a playground, to see what could work, but got nothing useful. I need a special constructor for CustomRectangle, which can initialize a CustomRectangle from a NativeRectangle. An alternative solution is to make a wrapper class that holds the original object as a property, but that wouldn't be my favorite solution, since I'd then have to map all methods and properties

class NativeRectangle: NSObject {
    var width: Int
    var height: Int

    init(width: Int, height: Int) {
        self.width = width
        self.height = height
        super.init()
    }
}

class CustomRectangle: NativeRectangle {
    var area: Int { return width * height}
}

let rect = NativeRectangle(width: 100, height: 20)

let customRect = CustomRectangle(rect) // This fails, i need a constructor

print(customRect.area)
3

There are 3 answers

0
zoul On

If you already work in the Objective-C land, there’s an option to wrap the native class and forward all (except the added) messages automatically:

- (NSMethodSignature*) methodSignatureForSelector: (SEL) selector
{
    NSMethodSignature *ours = [super methodSignatureForSelector:selector];
    return ours ?: [wrappedObject methodSignatureForSelector:selector];
}

I can’t remember if this is everything that was needed for the forwarding to work, but it should be pretty close. Also, I don’t know how this would play with Swift, so I guess we could consider this an interesting piece of trivia from the Objective-C days and look for a better solution…


A second, also slightly hacky option that comes to mind is using the associated objects feature to link the cached data to the original instance. That way you could keep your extensions approach.

2
Christopher Oezbek On

There is no way in Swift (and in general in most Object Oriented languages) to use an existing instance of a base class object when creating a child class instance.

From a general programming stand-point you have the two options in this situation:

  1. Use composition: Make the CustomRectangle contain a NativeRectangle and forward all methods to it that you need.

  2. Use a map to link NativeRectangles to additional information. In Objective C and Swift you can you objc_AssociationPolicy to have such an internal map most easily. See https://stackoverflow.com/a/43056053/278842

Btw. There is no way that you will see any speed-up from "caching" a simple computation as width * height.

2
Tushar Sharma On

You created your own CustomRectangle(object: rect) , so swift will not provide default init() any more. You explicitly need to call one of your own holding your property and make call to super.init(), as your class also inherits from super class. –

class NativeRectangle: NSObject {
    var width: Int
    var height: Int

    // Super class custom init()
    init(width: Int, height: Int) {
        self.width = width
        self.height = height
        super.init()
    }
}

class CustomRectangle: NativeRectangle {

    // computed property area
    var area: Int { return width * height}

    // Sub class Custom Init
    init(object:NativeRectangle) {
        // call to super to check proper initialization
        super.init(width: object.width, height: object.height)
    }
}

let rect = NativeRectangle(width: 100, height: 20)

let customRect = CustomRectangle(object: rect)

print(customRect.area) //2000