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) }
まず空の protocol を
AbstractClass
として定義します。つぎに protocol extension でジェネリクスを使用した
abstract
メソッドを定義します。普通に
fatalError
を呼ぶとその後の行が実行されないという旨の警告が出るので、プライベートメソッドとして内部でfatalError
を呼ぶだけのcustomFatalError
メソッドを定義します。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 は抽象クラスをサポートしないのかなー。