7

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)
Esben von Buchwald
  • 2,772
  • 1
  • 29
  • 37

3 Answers3

5

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.

Christopher Oezbek
  • 23,994
  • 6
  • 61
  • 85
  • Composition is usually the first thought that comes to my mind when I've had to do stuff like this. – Abizern Aug 26 '17 at 10:44
  • Thanks - When I'll have to make a wrapper class and map the properties, I guess... I was hoping for a more elegant solution. The simple "caching" of width*height was just for the example... – Esben von Buchwald Aug 27 '17 at 11:15
0

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.

zoul
  • 102,279
  • 44
  • 260
  • 354
0

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
Tushar Sharma
  • 2,839
  • 1
  • 16
  • 38
  • I don't thinkt that's a solution for EKEvents, as they dont have the proper constructor available. But for other types it might be useful. I'll stick to the recommendation by @Christopher Oezbek – Esben von Buchwald Aug 27 '17 at 11:16
  • @Esben von Buchwald not much aware about EKEvents or how initialization works for it.But above answer is basic initialization process followed in swift or most probably in any other languages. Indeed you can stick with Oezbek solution . – Tushar Sharma Aug 27 '17 at 11:29