先日、自分が発見した型制約付きプロトコルの実行時エラーの説明とその解消法について紹介します。
型制約付きプロトコル
Swift の Protocol は、宣言時に where
で準拠する型に制約を指定することができます。
準拠する型が制約を満たしていないと、コンパイラが、コンパイル時にエラーとして検知してくれます。
class SuperClass { }
// 型制約付きプロトコル
protocol SomeProtocol where Self: SuperClass { // 準拠する型は SuperClass を継承するように制約を付ける
func doSomething()
}
class SomeSubClass: SuperClass, SomeProtocol {
func doSomething() {
print("doSomething")
}
}
class InvalidSomeSubClass: SomeProtocol { // コンパイルエラー: 'SomeProtocol' requires that 'InvalidSomeClass' inherit from 'SuperClass'
func doSomething() {
print("doSomething")
}
}
型制約付きプロトコルによる実行時エラー
型制約付きプロトコルは、準拠する型に制約を付け、コンパイル時にその制約を検知できる便利な機能です。
しかし、型制約付きプロトコルゆえの実行時エラーも存在します。
class SuperClass { }
protocol SomeProtocol where Self: SuperClass {
func doSomething()
}
class SomeSubClass: SuperClass, SomeProtocol {
func doSomething() {
print("doSomething")
}
}
let sp: SomeProtocol = SomeSubClass() // SomeProtocol 型として保持する
sp.doSomething() // EXC_BAD_ACCESS で落ちる
エラーの原因は、SomeSubClass
のインスタンスを SomeProtocol
(型制約付きプロトコル) 型として保持して、そのメソッドを呼び出したことです。
SomeSubClass
は SomeProtocol
に準拠しているため、そのインスタンスを SomeProtocol
型として扱ってもコンパイルは通ります。
しかし、インスタンス sp
自体は、 SuperClass
を継承した型ではありません。そのため、 sp
が doSomething()
に実行時にアクセスすると、型制約を満たしていないため、エラーとして落ちてしまいます。
実行時エラーの解決
上記の実行時エラーを解決するための方法は、2通りあります。
1. インスタンスを型制約の型として実行する
let sp: SuperClass & SomeProtocol = SomeSubClass() // SuperClass & SomeProtocol 型として保持する
sp.doSomething() // "doSomething"
インスタンス sp
が実行時に型制約を満たしていないことがエラーとなっていたため、 sp
を型制約を満たすような型として実行すれば解決します。
2. 型制約付きプロトコルに @objc
属性を適用する
@objc protocol SomeProtocol where Self: SuperClass { // @objc を適用
func doSomething()
}
...
let sp: SomeProtocol = SomeSubClass() // SomeProtocol 型として保持する
sp.doSomething() // "doSomething"
なぜ @objc
の適用によって実行時エラーを回避するか理解はできていませんが、解決法として紹介されていました。
https://bugs.swift.org/browse/SR-7068
まとめ
エラー文が EXC_BAD_ACCESS
としか表示されず、原因を突き止めるのに時間がかかりました。Swift の Protocol は型制約を指定することができますが、実行時のエラーも考慮して使いましょう。