Make Optional Requirements Objective-C-only
01 何が問題だったのか
Objective-C のプロトコルには、適合する型が実装してもしなくてもよい optional 要件 という仕組みがあります。NSTableViewDelegate のようなデリゲートプロトコルに多数含まれており、Swift ではこれを表現するために optional キーワードが用意されています。
@objc protocol NSTableViewDelegate {
optional func tableView(_: NSTableView, viewFor: NSTableColumn, row: Int) -> NSView?
optional func tableView(_: NSTableView, heightOfRow: Int) -> CGFloat
}
ただし、この optional は @objc が付いたプロトコルでしか使えず、純粋な Swift のプロトコルでは書けません。見た目は Swift 一般の言語機能のようでありながら、実際は Objective-C 互換のための機能で、しかもその制約がキーワードからは読み取れない状況でした。そのため「なぜ Swift の普通のプロトコルでは optional が書けないのか、コンパイラのバグではないか」といった混乱が繰り返し持ち上がっていました。
Swift プロトコルに一般化できない理由
optional 要件を Swift のプロトコル全般に導入する案も検討されましたが、Swift にはすでに同じ目的をよりきれいに達成する手段があります。
- プロトコルエクステンションでデフォルト実装を与えれば、適合側が実装しなかったときの挙動を宣言時に決められます。
- オプショナルな部分だけ別のプロトコルに切り出し、必要な型だけが追加で適合するようにすれば、要件ごとの opt-in を表現できます。
これらの手段があるため、「要件ごとに実装されたかを動的に問い合わせる」という optional 要件のスタイルは Swift ではアンチパターンとされ、一般化は採用されない方向でした。
かといって完全に削除もできない理由
一方で、Cocoa のデリゲートやデータソースは optional 要件を多用しているため、Swift から言語機能ごと削除することもできません。Objective-C 側は実行時に -respondsToSelector: で実装の有無を問い合わせるコード生成モデルに依存しており、Swift 側の表現をいくら変えてもこのランタイム挙動は維持する必要があります。
optional 要件を「オプショナルなクロージャ型プロパティ」にマッピングし直す案もありましたが、複合名を持つプロパティの導入や、プロトコルのプロパティ要件をクラスのメソッドで満たすための追加仕様など、言語側への大きな拡張が必要になるうえ、「プロトコルの宣言をそのままコピペして実装する」という自然な使い方も壊してしまうため採用されませんでした。
結果として、optional 要件は Objective-C 互換機能として残し続けるしかなく、その事実をキーワード側から明確に伝える方法が求められていました。
02 どのように解決されるのか
optional キーワードを付ける要件には、宣言ごとに明示的な @objc 属性を書くことを必須にします。これにより、optional が Objective-C 互換のための機能であることがソース上ではっきりと読み取れるようになります。Swift 3.0 で実装されました。
@objc protocol NSTableViewDelegate {
@objc optional func tableView(_: NSTableView, viewFor: NSTableColumn, row: Int) -> NSView? // OK
optional func tableView(_: NSTableView, heightOfRow: Int) -> CGFloat
// error: 'optional' requirements are an Objective-C compatibility feature; add '@objc'
}
プロトコル全体に @objc が付いていても、個々の optional 要件にも @objc を明示する必要があります。逆に言うと、@objc が付かないプロトコルや要件では optional は書けず、Swift のプロトコルであるにもかかわらず optional 要件が書けてしまうかのような誤解が起きにくくなります。
既存コードへの影響
これまで @objc プロトコル内に optional 要件を書いていたコードでは、各要件に @objc を追記する必要があります。optional 要件への @objc の明示はこれまでも許されていたため、新しいコードを書けなくなるわけではなく、移行もマイグレータや Fix-It で機械的に対応できる範囲です。