今回は The Basics
における Error Handling
を見ていく中で気になった中で、話し切れていない最後のところ。エラーを表現するためのプロトコルの周りを眺めてみます。ちょっとした余韻的な話題ではありますけれど、それらはたとえば LocalizedError
みたいに幾つかあるのと、以前は Error
が特殊な性質を持っていたりしたこともあるので、そんな辺りをおさらいできたらいいなと思ってます。どうぞよろしくお願いしますね。
今回は参加者の一般参加者を募って、ゆめみの外の人たちも訪れての開催になる見通しです。
——————————————————————————————————— 熊谷さんのやさしい Swift 勉強会 #186
00:00 開始 00:40 エラーを簡単に通達するための手法 03:19 String をエラー型として扱う 03:56 import って必要なもの? 07:48 文字列をエラーとして送出 08:39 エラーを表現するためのプロトコル 11:17 リテラルから任意のエラー型への推論は可能? 14:22 なぜ ErrorProtocol ではないのだろう 14:52 既定の文字列型を変更したときの既定の実装 16:06 エラーの内容を文字列で取得するために 18:33 プロトコル要求を搭載する際の注意 20:00 Cocoa フレームワークにおけるエラーの扱い 20:38 CustomNSError の使い方と所感 22:43 エラーを文字列化する仕組みに違和感 26:25 LocalizedError を有効活用してみると良さそう 27:03 CustomNSError の不思議な立ち位置 28:44 クロージングと次回の展望 ———————————————————————————————————
Transcription & Summarize : 熊谷さんのやさしい Swift 勉強会 #186
エラーハンドリングについてのお話です。前回の勉強会ではエラーハンドリングについて触れ、Swiftのエラーを気軽に使うには独自のエラー型を定義する必要があるという点について話しました。Swiftでエラーを扱う場合、String
型をLocalizedError
プロトコルに準拠させることによって、文字列リテラルをそのままエラーとして投げることができるという利点があります。
これはバッチファイルやシェルスクリプトのようなスクリプト的な用途でSwiftを使う場合に便利です。このような時にエラーメッセージを簡単に扱うことができます。
他のプログラミング言語にも、例えばJavaScriptなど、文字列インスタンスをそのまま例外として投げることができるものがあります。こうした機能があると、純粋な品質担保をしなければならないソフトウェアの場合、精密なエラーハンドリングが要求される場合でも便利です。
前回に引き続き、今回はエクステンションについても触れていきます。まず最初に普通にエラーをスローしてみましたが、String
がエラー型に準拠していないためエラーが発生しました。しかし、String
に対してLocalizedError
をエクステンションで準拠させると、これでエラーとして投げられるようになります。
ここで一つ疑問に思ったのは、Swiftのコードでインポートが必要な場合です。特にFoundation
などのフレームワークをインポートする必要がある場合です。名前空間の都合上、現在は必要ですが、将来的にこれをなくす良い手段がないか考えることがあります。インポートが自動化できると便利ですが、現在でも例えばmacOS
アプリでは、Foundation
やAppKit
などは自動的にインポートされる設定がされていることがあります。
プロジェクト設定で自動的にフレームワークをインポートする設定があり、リンクフレームワーク
をチェックすると必要なインポートが補助されることもあります。また、使っていないフレームワークのインポートを検出するサポートもあった方が便利でしょう。
今回は、エラーをスローした際の流れを確認し、キャッチしていないエラーの処理についても一併に触れました。このように、エラーハンドリングはSwiftでも他の言語でも非常に重要で便利な機能です。 エラーメッセージをそのまま表示するだけでも、非常に便利だという話です。多くのソフトの場合、エラーメッセージをそのまま表示することができます。特にクリプト的な処理をするときに、エラーメッセージをそのまま投げて表示できるのは非常に便利です。
次に、興味深かったのが「ローカライズドエラー」についてです。通常、Swift標準では Error
プロトコルを使用します。このプロトコルを使用してもコードは正常に動作します。しかし、自分はこれまで Error
プロトコルか、または NSError
しか使ったことがありませんでした。記憶が曖昧ですが、NSError
が Error
を拡張しているかどうか分かりません。でも、ここで面白いのは「ローカライズドエラー」があることです。自分はこの2つのプロトコルしか把握していませんでしたが、LocalizedError
というものがあるとは知りませんでした。これを詳しく見ていきたいと思います。
ところで、「ローカライズドストリングリソース」というのが Foundation
にありますが、これは多分関係ないでしょう。他にもいくつかあるようですが、今回はこの2つを眺めてみたいと思います。
その前に、ちょっと気になった箇所があるので試してみていいですか?例えば、この String
リテラルを使って、もしエラーに対応しているならどうなるか試してみたいです。エラーに対応した型が ExpressibleByStringLiteral
に準拠していたらどうなるかという話です。普段は struct MyError: Error
のようにして Error
プロトコルに準拠させますが、さらに ExpressibleByStringLiteral
も追加してみます。使ったことがある人いますか?
やってみましょう。例えば、これで MyError
型を String
リテラルとして初期化できるかどうか試したいです。ですが、これ厳密に言うと String
に解釈されちゃいますね。つまり、タイプチェッカーが暗黙的に解釈するようになってほしかったのです。そうすると String
を汚さずに、より特定のエラー型として利用できるので便利です。
しかし、残念ながら、Swift
標準のプロトコル Error
の方に引っ張られてしまうようです。これを上手く隠蔽できればよいのですが、今度は名前空間の考慮が必要になり微妙です。
APIデザインガイドラインでは、なぜ Error
プロトコルにしなかったのだろうといつも疑問に思います。両方を Error
としたとき、APIデザインガイドラインに沿っているのだろうかと。
最後に、少し残念だったのは、String
リテラルタイプに対してエラーを設定することができなかったことです。ただし、これをやることは一般的には適切ではないとも思います。エラーを設定するときは、それ専用の型を作るのが正しいでしょう。少し試してみて、おかしい部分があったらディフォルト実装を見直さなければなりません。
以上で、今回は Error
プロトコルに関する実験とその考察についての話でした。 さて、ここに戻ってプロトコルについて細かく見ていきましょう。まずはエラープロトコルについてです。
Stringsのインスタンスにエラーメッセージが正しく表示されたのに対して、自分で定義したエラー型MyError
を使うと、エラーメッセージがうまく表示されないことがあります。その際に役立つのがLocalizedError
プロトコルです。これは特定のエラー型を定義する際に、エラーメッセージをしっかりと表示させるのに使います。
例えば、エラー型MyError
を作成し、それを投げたときにどういうエラーメッセージが出るのか確認してみましょう。以下のコードで試してみます。
enum MyError: Error {
case somethingWentWrong
}
func throwError() throws {
throw MyError.somethingWentWrong
}
do {
try throwError()
} catch {
print(error)
}
上記の例では、エラーメッセージが正しく表示されないことがあります。その理由はLocalizedError
プロトコルが実装されていないからです。このプロトコルを追加すると、詳細なエラーメッセージを得ることができます。
次にLocalizedError
プロトコルをMyError
に追加してみます。
enum MyError: LocalizedError {
case somethingWentWrong
var errorDescription: String? {
switch self {
case .somethingWentWrong:
return "Something went wrong"
}
}
}
こうすることで、エラーメッセージがちゃんと表示されるようになります。また,LocalizedError
プロトコルを実装しないと、エラーメッセージがインスタンスそのものの型情報になってしまい、詳細なメッセージを得る手段がなくなります。
一方で、カスタムNSError
プロトコルを使う方法もあります。こちらも試してみましょう。
enum MyError: Error {
case somethingWentWrong
var nsError: NSError {
let userInfo: [String: Any] = [NSLocalizedDescriptionKey: "Something went wrong"]
return NSError(domain: "com.example.MyError", code: 1, userInfo: userInfo)
}
}
このプロトコルを使うと、NSError
と互換性があるエラーを作成できます。NSError
は、errorDomain
やerrorCode
、userInfo
といった情報を含んでおり、これにより詳細なエラーメッセージを提供できます。
自分も以前はずっとこの方法を使っていました。ですが、LocalizedError
を使う方がよりSwiftの標準的な方法であり、多くの人にとって馴染みやすいかもしれません。LocalizedError
プロトコルを使うことで、エラーメッセージを統一し、エラーハンドリングがしやすくなります。
NSLocalizedDescriptionKey
などを使ってユーザーインフォを設定するのも便利ですが、やはりSwiftらしい方法ではないかもしれません。LocalizedError
の方が使いやすく、これからはこの方法を使っていくことをお勧めします。 エラープロトコルについての話をしていきます。Swiftのエラープロトコルには規定の実装は特にないんですが、カスタムのエラーは自由に作れますね。例えば LocalizedError
というプロトコルがあります。これを適切に実装すると、ローカライズされたエラーメッセージを提供できます。Foundation
にあるので、必要に応じて利用できるようになっています。
エラーを処理する際、LocalizedError
を使うとエラーメッセージを統一的に取得できます。すべてのエラーは String
に変換できるという契約があるので、エラーを String
に変換する際に CustomStringConvertible
を使うこともあります。このインスタンス自体を文字列化したい場合に便利です。
LocalizedError
を適用することで、使い勝手が良くなる場合があります。むしろ、エラーを設計する際に LocalizedError
を使う癖をつけるのが良いでしょう。具体的な例を以下に示します。
struct MyError: LocalizedError {
var errorDescription: String? {
return NSLocalizedString("Something went wrong", comment: "")
}
}
また、NSError
との互換性もあります。Swiftの Error
は NSError
にキャストできますし、逆も可能です。カスタムの NSError
を使う理由は少ないかもしれませんが、互換性を保つために用意されているのかもしれません。
今日はこの程度にして、次回はエラープロトコル周りのもう少し面白い話をしていこうと思います。お疲れ様でした。ありがとうございました。