Having come across problems when sub-classing UIKit classes and adding immutable variables to them, I made a test project to figure out what was going on.
My conclusion is that if
- we have an Objective C class, which inherits from another class, with its own designated initialiser (implicit or explicitly annotated)
- in its initialiser, it calls [self initWithXX] where initWithXX is an init method on the superclass
- we subclass this class in Swift, adding an immutable property (which obviously must be initialised on instantiation)
- we implement a single designated initialiser for this Swift class which sets the immutable property then calls the parent class designated initialiser
then this will cause a runtime exception because the Swift class, when calling the Objective C superclass's designated initialiser, will attempt to call initWithXX on self and this method has not been inherited from the superclass because we have implemented a designated initialiser.
The code for this test would be:
View.h
#import <UIKit/UIKit.h>
@interface View : UIView
-(instancetype)initWithIdentifier:(NSString *)identifier;
@end
View.m
#import "View.h"
@implementation View
-(instancetype)initWithIdentifier:(NSString *)identifier {
self = [self initWithFrame:CGRectZero];
if (self) {
if ([identifier length] > 0) {
return self;
}
}
return nil;
}
-(instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
return self;
}
return nil;
}
@end
SwiftView.swift
import Foundation
import UIKit
class SwiftView: View {
let name: String
init(name: String) {
self.name = name
super.init(identifier: "test")
}
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Exception generating code
let view: SwiftView = SwiftView(name: "test")
Exception
fatal error: use of unimplemented initializer 'init(frame:)' for class 'Test.SwiftView'
My motive behind this was sub-classing standard library classes (UITableViewController, MKAnnotationView...) and adding immutable properties to contain injected dependencies on them, which then needed to be initialised on instantiation of the sub-class by passing them in to a custom designated init method, and finding that this caused the above, unexpected exception.
Having done this experiment to determine the causes, I think that I understand why it is happening (because the parent Objective C class designated initialiser is delegating to another designated initialiser of its own, rather than calling its superclass's initialiser). It would seem obvious that to fix this, the standard libraries should change their initialiser methods to call superclass initialisers instead of self initialisers, but I can appreciate that calling self instead of super may be valid functionality in order to allow subclasses to override default behaviour while preserving particular initialisers. Also, I would imagine that such a change may cause lots of problems because many apps may have built on this functionality, and this would break it. I suppose the basic problem here is a language feature incompatibility between Objective C and Swift.
Can anyone see any way around this problem (either in terms of an Apple fix or code workarounds) other than the (very undesirable) way of making these immutable variables mutable and setting them in the second initialisation phase?