一から勉強させてください( ̄ω ̄;)

最下級エンジニアが日々の学びをアウトプットしていくだけのブログです。

Swiftで抽象クラスを実装する

Swiftではprotocolがサポートされていて、protocol extensionもできるようになりましたが、抽象クラスは2015年11月現在サポートされていません。

僕は抽象クラスがほしいなーって思うときがたまにあって、例えばtypealiasを使用しているprotocolを何かの変数の型に指定しようとすると、protocol can only be used as a generic constraint...みたいなエラーが出るんですよね。抽象的に実装したいのにできねえ...ってなります。

そういった現状ですが、前回の記事でも使用したRxSwiftで抽象メソッドをトリッキーに実装しているのを見て感動したので、真似して抽象クラスをつくってみました。

AbstractClassの実装

protocol AbstractClass: class {}

extension AbstractClass {
    func abstract<T>() -> T {
        customFatalError("Must be overridden")
        let dummyValue: T? = nil
        return dummyValue!
    }
}
private func customFatalError(message: String) {
    fatalError(message)
}
  1. まず空のprotocolをAbstractClassとして定義します。

  2. つぎにprotocol extensionでジェネリクスを使用したabstractメソッドを定義します。

  3. 普通にfatalErrorを呼ぶとその後の行が実行されないという旨の警告が出るので、プライベートメソッドとして内部でfatalErrorを呼ぶだけのcustomFatalErrorメソッドを定義します。

  4. 3で用意したメソッドを呼んでoverrideが必要な旨とともにfatalErrorを出しつつ、ダミーのOptional値を返すことでコンパイルが無理矢理通るようにします。これでfunc foo() -> Int { return self.abstract() }のように何かの型を返したい抽象メソッドを定義する場合でもコンパイルエラーにはなりません。

実際の使用例はちょっと適当ですが、以下のようなイメージです。

class Employee: AbstractClass {

    let name: String
    private let salary: Int

    init(name: String, salary: Int) {
        self.name = name
        self.salary = salary
    }

    func work() {
        return self.abstract()
    }

    func getSalary() -> Int {
        return self.salary
    }

}

class Engineer: Employee {

    override func work() {
        print("Writing awesome code!!")
    }
    
}

AbstractClassに準拠しているので「このクラスは抽象クラスだ」ってぱっと見でわかるし、「self.abstract()を返しているメソッドは抽象メソッドなんだな」ってなんとなくわかります。

そしてclass Foo<T>: AbstractClass {}みたいなジェネリクス付きの抽象クラスを定義をして、変数の型にFoo<String>などと指定しても前述のprotocolのようなエラーは出ません。

ただJavaとかだと抽象クラスをインスタンス化しようとするとコンパイルエラーになると思いますが、これはあくまで抽象クラスもどきなのでインスタンス化しようとしてもコンパイルエラーにはなりませんのでご注意を。。

RxSwiftの抽象メソッドの実装がさらに進化してた

上記の実装は1~2ヶ月前に見て真似したものだったのですが、最近RxSwiftを見なおしてみると、@noreturnを使うことでより簡潔になっていました。 @noreturnは呼び出し元に戻ってこない旨を表す属性で例えばfatalError

@noreturn public func fatalError(@autoclosure message: () -> String = default, file: StaticString = default, line: UInt = default)

のように@noreturnメソッドとして定義されています。これを使えばより簡潔に書けそうです。

以下が書きなおしたバージョン。

protocol AbstractClass: class {}

extension AbstractClass {
    @noreturn func abstract() {
        fatalError("Must be overridden")
    }
}
class Employee: AbstractClass {

    let name: String
    private let salary: Int

    init(name: String, salary: Int) {
        self.name = name
        self.salary = salary
    }

    func work() {
        // return self.abstract()
        self.abstract()
    }

    func getSalary() -> Int {
        return self.salary
    }

}

class Engineer: Employee {

    override func work() {
        print("Writing awesome code!!")
    }
    
}

まとめ

Swiftは抽象クラスをサポートしないのかなー。

参考