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")
    }

}

You’ll go from seeing this:
Screen Shot 2018-08-14 at 9.24.48 AM.png

To seeing this:
Screen Shot 2018-08-14 at 9.25.27 AM.png

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...
    }

}

This looks like this:
Screen Shot 2018-08-14 at 9.27.24 AM.png

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.

 
27
Kudos
 
27
Kudos

Now read this

WindCheck 2.4

Since my last post talking about the revival of the WindCheck EFB, WindCheck has come a long way. The app now displays airport runways, airport diagrams, instrument procedure charts, a map, NOTAMs, TFRs, and much more. WindCheck makes... Continue →