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
foo.bar = 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:
MKGeodesicPolyline's initialisers are not inits, but static methods:
[+ polylineWithPoints:count:]
[+ polylineWithCoordinates:count:]
That means you can't subclass this class (which is unfortunate).
That this class is not meant to be subclassed is not mentioned in the documentation.
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).
class Bar: MKMultiPoint {
override func points() -> UnsafeMutablePointer<MKMapPoint> {
fatalError("TODO")
}
override var pointCount: Int {
fatalError("TODO")
}
}
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*.