George Garside Blog

A place of many ramblings about macOS and development. If you find something useful on here, it's probably an accident.

Some classes can’t be subclassed since the init is marked as unavailable in objc, which can’t be overridden in Swift. Fortunately there’s a workaround so you can super.init() from your subclass.

Solution

The fix for this issue is to introduce a class into the inheritance hierarchy with an init. This init needs to meet three requirements:

  1. The new init can’t override the parent init. The init is marked unavailable in the parent class and that includes subclasses.
  2. The new init can’t have zero parameters. The parent init has zero parameters, so adding your own init with zero parameters would require ‘override’, breaking requirement 1.
  3. The new init can’t call the parent init. That would cause the same problem with the class that currently exists with your subclass — the problem’s just been moved one class up.

Example

Suppose the following Swift class that tries to extend an Objective-C class from the CoreBluetooth framework, CBPeripheral:

class Peripheral: CBPeripheral {
    override init() {
        print("init")
    }
}Code language: Swift (swift)

This class overrides the parent class’s init, which has been marked unavailable, therefore presenting an error.

Cannot override ‘init’ which has been marked unavailable.

Apple's documentation for CBPeer also tells you to not do this:

You typically don’t create instances of either CBPeer or its concrete subclasses. Instead, the system creates them for you during the process of peer discovery.

CBPeer Overview — Apple Documentation

Let’s add a parent to the hierarchy. This parent is a new class we create,

class CBPeripheralInitable: CBPeripheral {
    init(_: Void = ()) {}
}

class Peripheral: CBPeripheralInitable {
    init() {
        print("init")
    }
}
Code language: Swift (swift)

Adding an ‘initable‘ class to the hierarchy using init(_: Void = ()). This init doesn't override the NSObject init which takes no arguments, since there is one argument. This Void argument could be another type, but using Void prevents accidentally passing an argument. A default value for the argument of () alongside no parameter name means the call site stays the same: init().

Leave a Reply

0

I can't get it to work in Xcode 12.5 🙁 Is this workaround outdated already?

Reply