Swift2.0からErrorTypeが登場し、enum等に適応させることでエラーの種類を簡単に実装できるようになりました。
enum SomeErrors: ErrorType {
case ErrorA
case ErrorB
case ErrorC(String)
}
func doSomething() throws {
throw SomeErrors.ErrorA
}
//----- (A)
do {
try doSomething()
} catch let error {
print(error)
}
//------ (B)
do {
try doSomething()
} catch SomeErrors.ErrorA {
print("error!")
} catch SomeErrors.ErrorC(let msg) {
print("error! \(msg)")
}
といった感じでdo~try~catch
でエラーをキャッチしたときにその内容をprintできたり(A)
、パターンマッチで特定のエラーをキャッチする(B)
が簡単に行なえます。
また、よくあるResult型などで失敗時にErrorTypeを突っ込むことができたりします。
(B)
のようにパターンマッチで条件を分けたい場合だけなら問題ないのですが、(A)
のようにエラーの内容をprintしようと思うとちょっと困ったことがあります。
問題点
実際に(A)
のパターンでerrorをprintすると、ErrorA
と出力されます。 たったそれだけです。
ちなみに、ErrorTypeはNSErrorへのas
を使ったキャストが許容されていて、
print(error as NSError)
としてあげると、
"Error Domain=SomeErrors Code=0 "(null)"
と、少しだけ出力が変わります。NSErrorと同じ形式ですね。
ただ、これも問題があって、これが仮にErrorB,ErrorC
だとしても、 domain 、 code 、 userInfo は同じものになってしまいます。
これも不便。。
なので、
- 独自の domain や code をenumのcase毎に付けられるようにしたい/変えたい
- userInfo を付与できるようにしたい
- 普通にprintした時にも
NSError
と同様の出力をしたい as NSError
でキャストした場合にも独自に設定した domain 、code を適応したい
といった点をprotocolを使って解決します。
実装
今回はgistのembedを試してみました。
使ってみる
先ほどのSomeErrors
をEnhancedErrorType
に準拠させて、必要な処理を追記していきます。
enum SomeErrors: EnhancedErrorType {
case ErrorA
case ErrorB
case ErrorC(String)
var domain: String {
return "SomeErrorCustomDomain"
}
var code: Int {
switch self {
case .ErrorA: return -10000
case .ErrorB: return -10001
case .ErrorC( _): return -10002
}
}
var userInfo: [NSObject: AnyObject]? {
switch self {
case .ErrorA:
return [NSLocalizedDescriptionKey: "ErrorA occurred."]
case .ErrorB:
return [NSLocalizedDescriptionKey: "ErrorB occurred."]
case .ErrorC(let msg):
return [NSLocalizedDescriptionKey: "ErrorC occurred. message: \(msg)"]
}
}
}
func doSomething() throws {
throw SomeErrors.ErrorC("wahaha")
}
do {
try doSomething()
} catch let error {
print(error as NSError) // 1
print(error) // 2
print((error as! SomeErrors).toNSError()) //3
}
結果として、
1) Error Domain=SomeErrorCustomDomain Code=-10002 "(null)"
2) Error Domain=SomeErrorCustomDomain Code=-10002 "ErrorC occurred. message: wahaha" UserInfo={NSLocalizedDescription=ErrorC occurred. message: wahaha}
3) Error Domain=SomeErrorCustomDomain Code=-10002 "ErrorC occurred. message: wahaha" UserInfo={NSLocalizedDescription=ErrorC occurred. message: wahaha}
が得られます。
解説
まず、NSErrorに必要な domain 、 code 、 userInfo をprotocolに宣言します。
public protocol EnhancedErrorType: ErrorType {
var domain: String { get }
var code: Int { get }
var userInfo: [NSObject: AnyObject]? { get }
}
そして、extensionを使ってデフォルトの挙動を与えて、使う側が全て実装しなくても動くようにしておきます。
extension EnhancedErrorType {
var domain: String {
return String(reflecting: self.dynamicType)
}
var code: Int {
return 0
}
var userInfo: [NSObject: AnyObject]? {
return nil
}
}
加えて、NSErrorに変換するtoNSError()
も定義します。
public func toNSError() -> NSError {
return NSError(domain: domain, code: code, userInfo: userInfo)
}
これで使う側が独自に設定したい domain 、 code 、 userInfo を別途宣言しなおせば、toNSError()
を介してしっかりとしたNSErrorを吐き出すことができます。
ですが、まだ問題点が2つ残っていますね。
- 普通にprintした時にも
NSError
と同様の出力をしたい
こちらは、EnhancedErrorType
に、CustomStringConvertible
を継承させて、extensionでdescription
を定義します。
public protocol EnhancedErrorType: ErrorType, CustomStringConvertible {
// 省略
}
extension EnhancedErrorType {
// 省略
var description: String {
return "\(toNSError())"
}
}
これで、
do {
try doSomething()
} catch let error {
print(error)
}
としたときも、NSError
と同様の出力結果が得られるようになります。
as NSError
でキャストした場合にも独自に設定した domain 、code を適応したい
さて、ここまで処理を追記することで、よりNSErrorに近づいたわけですが、
print(error as NSError) // Error Domain=SomeErrors Code=0 "(null)"
とした時に、まだ出力結果が おかしいまま です。
これをどうにかするには、ErrorTypeが (隠し) 持つ _domain 、 _code を実装し直してあげます。
現在はオープンソースでErrorTypeの実装を見ることができ、ここで、 _domain 、 _code が実装されているのを確認することができます。
extension EnhancedErrorType {
var _domain: String {
return domain
}
var _code: Int {
return code
}
// 省略
}
これで、 domain 、code で宣言したものが、 _domain 、_code でも使われるようになるので、無事
print(error as NSError) // Error Domain=SomeErrorCustomDomain Code=-10002 "(null)"
と表示されるようになります!
このNSErrorのキャストの場合、userInfoだけは、どうしても欠けてしまいますが。
この変換っぽいことを定義している箇所が、おそらくこの辺りかと思います。
これで、よりErrorTypeをNSErrorに近づけつつ、使えるようになるんじゃないかなと思います!
Gistはこちらです。