Class being converted to superclass on runtime causes a crash

I've been working on a MapKit related project and I've run into a weird case where right after initialisation my class is converted to the superclass, which, in my scenario, crashes the app on the next line.

Demo code:

class Foo: MKGeodesicPolyline {
  var bar: Int?

let lcs: [CLLocationCoordinate2D] = [.init(latitude: 0, longitude: 0)]
let foo = Foo(coordinates: lcs, count: lcs.count)
print(type(of: foo)) // MKGeodesicPolyline instead of Foo = 2 // Crashes here

For some reason, this outputs MKGeodesicPolyline instead of Foo before crashing with EXC_BAD_ACCESS, presumably because the memory for the fields of Foo wasn't allocated. Even weirder, this doesn't seem to happen with the regular MKPolyline. Am I missing something here or have I ran a compiler bug?

In Objective-C, init returns a value. Typically this is self or nil, but it's allowed to return an unrelated object.

Swift assumes that it always returns self. I think the synthesized initializer call sets bar before calling super.init, and then super.init proceeds to throw out self and return a different object. That would explain why you're seeing MKGeodesicPolyline instead of Foo for the type, but I'm not sure whether there's anything you can do about it.


There are several unfortunate factors here at play:

  1. MKGeodesicPolyline's initialisers are not inits, but static methods:
    [+ polylineWithPoints:count:]
    [+ polylineWithCoordinates:count:]
    That means you can't subclass this class (which is unfortunate).

  2. That this class is not meant to be subclassed is not mentioned in the documentation.

  3. Swift happily allows you calling that initialiser with your subclass, this looks like a bug.

Interestingly you can call Foo() and it will return you your instance - but then how'd you initialise the points... perhaps using some obj-c trickery of accessing the fields that are not publicly accessible. Looks it would be better if you used containment rather than subclassing or associate your extra data field by other means (e.g. associated object).


Btw, the same happens in a pure Objective-C program, so this is not a Swift issue:

@import Foundation;
@import MapKit;

@interface Foo: MKGeodesicPolyline
@property int bar;

@implementation Foo

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        CLLocationCoordinate2D lcs[] = { { 0, 0} };
        Foo *foo = [Foo polylineWithCoordinates:lcs count:1];

        NSLog(@"%@", foo.class);
        // MKGeodesicPolyline = 2;
        // *** Terminating app due to uncaught exception 'NSInvalidArgumentException',
        // reason: '-[MKGeodesicPolyline setBar:]: unrecognized selector sent to instance 0x600002909ce0'
    return 0;

A further thought: try to subclass higher in the hierarchy?

MKGeodesicPolyline → MKPolyline → MKMultiPoint

class Bar: MKMultiPoint {
    override func points() -> UnsafeMutablePointer<MKMapPoint> {
    override var pointCount: Int {

Indeed. Which raises a question of its own why have "instancetype" in those methods... Looks like it should've been MKGeodesicPolyline* – in that case compiler would not allow assigning polylineWithPoints result to Foo*.

@interface MKGeodesicPolyline : MKPolyline

+ (instancetype)polylineWithPoints:(const MKMapPoint *)points count:(NSUInteger)count;
+ (instancetype)polylineWithCoordinates:(const CLLocationCoordinate2D *)coords count:(NSUInteger)count;


I've seen + (instancetype)xxx pattern in many places. Someone with better Obj-C knowledge might shed a light on that pattern validity.

I have no idea what is happening here, but subclassing MKPolyLine seems to be “difficult”: