はじめに
Swiftでプロトコルを定義し、Objective-Cからも利用可能にするために @objc
属性を付けることがあります。その際、プロトコルメソッドで特定の型のエラーをスローする Typed throws を使おうとすると、Swiftコンパイラ自体がクラッシュしてビルドが失敗することがあります。
発生バージョン
XCode 16.2
Swift version 6.0.3 (5.10 モード)
コンパイラ自体がクラッシュする条件
以下のように、@objc
属性が付いたプロトコル HogeProtocol
を定義し、そのメソッド HogeFunc
で特定の FugaError
型のみをスローするように throws(FugaError)
と指定したとします。
import Foundation
// エラー型を定義
enum FugaError: Error {
case someError
}
// @objc属性付きのプロトコルを定義
@objc
protocol HogeProtocol {
// Typed throwsを宣言
func HogeFunc() throws(FugaError) // <- ここが問題
}
// プロトコルに準拠したクラス
final class Hoge: HogeProtocol {
func HogeFunc() throws(FugaError) {
// 何らかの処理
throw FugaError.someError
}
}
このコードをコンパイルしようとすると、以下のような問題が発生します。
Swiftコンパイラ (swift-frontend) が内部でエラーを起こしてクラッシュし、ビルドが失敗します。
Command EmitSwiftModule failed with a nonzero exit code
詳細なログを見ると、以下のようなスタックトレースが出力されていました。
Please submit a bug report (https://swift.org/contributing/#reporting-bugs) and include the crash backtrace.
Stack dump:
0. Program arguments: /Applications/Xcode-16.2.0.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/swift-frontend ... (略) ...
1. Apple Swift version 6.0.3 (...)
2. Compiling with effective version 5.10
3. While generating Clang header
4. While printing forward declarations needed by members of 'HogeProtocol' (...)
5. While printing for member 'HogeFunc()' (...)
6. While walking its interface type, currently at 'FugaError' (...)
7. While printing NULL type!
... (以下スタックトレース) ...
おそらくの原因
根本的な原因は @objc
プロトコル内で Typed throws を使用していることにあります。
Objective-Cの NSError* には、Swiftの throws(SpecificError)
のような「特定の型のエラーのみをスローする」という概念がありません。Objective-Cから見ると、メソッドがエラーをスローするかどうかは分かりますが、そのエラーの具体的な型まではコンパイル時には分かりません。
そのため、@objc
で公開されるプロトコルのメソッド定義で Typed throws を使うと、Objective-Cとの互換性が取れず、Objective-Cヘッダー生成の段階でコンパイラがクラッシュするようです。
解決策
この問題を解決するには、@objc
プロトコルのメソッド定義では、型を指定せずに throws
だけを使用します。
import Foundation
enum FugaError: Error {
case someError
}
@objc
protocol HogeProtocol {
// 型を指定せず、単に throws と記述する
func HogeFunc() throws
}
final class Hoge: HogeProtocol {
// 実装クラス側では、throws(FugaError) と型を指定しても良いし、
// 単に throws としても良い。
func HogeFunc() throws(FugaError){
print("HogeFunc called")
throw FugaError.someError
}
}
ポイント:
-
プロトコル定義:
@objc
プロトコル内ではthrows
のみを使用します -
クラス実装: プロトコルに準拠するクラス側では、メソッドの実装で
throws(SpecificError)
と型を指定しても問題ありません
まとめ
Swiftで @objc
属性を付けたプロトコルを定義する際には Typed throws の使用はできません。
Objective-Cとの連携が必要な場面でエラーハンドリングを含むプロトコルを設計する際には注意してください