Un-implemented Methods in Swift
A great feature of Swift is that you can require that subclasses of a class implement a method. This can be achieved with required
. However, if you’re on the other side wanting to subclass something with no intention to implement all the required methods then it’s a lousy situation and this has bothered me for a while. Xcode recommends by default that you crash the app. A super famous example is this:
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
A lot of of the iOS SDK classes are NSCoding
, which means they can be serialized and deserialized. If you subclass UIButton
for example then you’ll have to implement the method above because UIButton
is NSCoding
. By default, Xcode fills the implementation of your method with fatalError("init(coder:) has not been implemented")
, which suggests that if you don’t plan on implementing the method then the best practice is to crash the app when the method is called.
I used to do this all the time but when you’re writing a framework that you plan to share, like a CocoaPod for example then it’s super important to have a clean and simple API where it’s obvious what methods can be called or not. The above practice doesn’t achieve that.
Marking Required Methods Unavailable #
So I’ve decided to simply use the @available
attribute. Typically this is used to test for availability of features or mark features as only available for certain platforms and versions. You can also use it to mark a method completely unavailable. Let’s say I want to subclass UIButton
without implementing deserialization, then it would look like this:
class MyButton : UIButton {
@available(*, unavailable)
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
The above allows you to both comply with the requirement and hide the method from autocompletion. You’ll also get an error at compile-time if you try to use this method.
Superclass Methods #
People who use your custom button however will still be able to call MyButton(frame: CGRect)
because that method is implemented by UIButton
. It turns out you can prevent others from calling superclass methods too! You just need to override the method and mark that as unavailable.
class MyButton : UIButton {
@available(*, unavailable)
override init(frame: CGRect) {
fatalError("init(frame:) not implemented")
}
@available(*, unavailable)
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
And now you can provide your custom initializer and ensure that it is the only initializer anyone can call:
class MyButton : UIButton {
@available(*, unavailable)
override init(frame: CGRect) {
fatalError("init(frame:) not implemented")
}
@available(*, unavailable)
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
init(custom: String, color: UIColor) {
// do stuff...
}
}
Objective-C Compatibility #
Now what happens with Objective-C compatibility? the @available
attribute as used in my example, will result on methods appearing as NS_SWIFT_UNAVAILABLE
and Xcode should show an error if any Objective-C code tries to call it.
Note you can’t actually prevent Objective-C code some using a method because it’s just message-passing and there is no compile-time safety except for friendly warnings.