一から勉強させてください

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

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 は抽象クラスをサポートしないのかなー。

参考