swiftのErrorとNSErrorの違いがよく分からないので自分なりにまとめてみた

#はじめに
いつも、swiftで開発しているのですが、Error・NSErrorの2つのエラーオブジェクトが出てきて
いつも何で分けられてるの？と疑問に思いながらErrorからNSErrorに型キャストしたりしてました。

よく分からないままだと流石にまずいなと思い、Qiitaに備忘録として残そうと思った感じです。

#ErrorとNSErrorの違いとは
そもそも、この2つの違いは何でしょうか？

調べてみると

・NSErrorは`Objective-C`のエラーオブジェクトで`クラス`として定義されている。

・Errorは`swift`のエラーオブジェクトで`プロトコル`として定義されている。

という感じでした。

更に、深く調べてみましょう。
#NSError
[NSError](https://developer.apple.com/documentation/foundation/nserror)は`Objective-C`で使われていたエラーオブジェクトで`エラー状態に関する情報`が含まれています。

どんな情報が含まれているのかというと
`domain`・`code`・`userInfo`という3つの情報が含まれています。

分かりやすようにまとめてみました↓

|情報|意味|
|:---:|:---:|
|domain|エラーの種類を識別するための文字列|
|code|エラーの種類を識別するための整数値|
|userInfo|エラーに関する付加情報|

swiftのErrorはNSErrorに常に`キャスト`できるようになっており、それらの情報を`参照`することができます。

実際にコードを書いてみるとこんな感じです↓

```swift
    func manager(error: Error) {
        // ErrorからNSErrorにキャスト
        let nsError = error as NSError
        // 欲しい情報を参照する
        print(nsError.domain)
        print(nsError.code)
        print(nsError.userInfo)
    }
```
例えば何かのマネージャークラスのデリゲートメソッドがあって
エラーが発生したら呼び出されるメソッドがあるとします。

そしたら、そのエラーを`NSError`にキャストして欲しい情報を`参照`していくという流れですね。

#NSErrorの使いどころとは
swiftでは`Objective-C`との互換性を保つために存在していますが、swiftのエラーハンドリングが強力なのもあって、NSErrorで使っていたdomainやcodeを使う機会は減ってしまったそうです。

ただ、`2つの言語が混在しているプロジェクト`であればNSErrorを意識する場面に直面するそうですね。

私は、全くObjective-Cを触ったことがないので分かりませんが自分の開発しているアプリでは
domainやcodeの情報が欲しい時があるので割とキャストして使っています。

#Error
最初の方で述べたように`swift`で使われているエラーオブジェクトで`プロトコル`として定義されています。

そんなswiftの[Error](https://developer.apple.com/documentation/swift/error)は`準拠した型がエラーを表現する型として扱えることを示すためのプロトコル`であって
準拠するために必要な実装はないです。

つまり、swiftのErrorは`エラー情報を表現するプロトコル(規約)`なんですね。

因みに余談ですが、swift2ではErrorは`ErrorType`という名前だったそうで
swift3から`Error`という名前に変わったそうです。

#Errorの使いどころとは
このErrorプロトコルに準拠する型は、`列挙型(enum)`に準拠するのが一般的で
`発生するエラーをまとめて記述できる`というメリットがあるからです。

更に、プログラム全体で起こり得るあらゆるエラーを一つの列挙型で定義せずに
`エラーの種類によって別の型を定義する`のが常だそうですね。

では実際に、Errorプロトコルを準拠して列挙型で定義してみましょう。

今回は、API通信を行う時に発生するであろうエラーを列挙型で定義してみます。

```swift
enum ApiError: Error {
    case networkError
    case decodeFailed
    case responceFailed
    case unknown
}
```

このように定義することができます。

後は`引数`を持たせて、このような使い方が出来ます。
以下の例では、computedプロパティを使って、ケースごとにメッセージを返すようにしています↓

```swift
enum ApiError: Error {
    case networkError
    case decodeFailed(Error)
    case responceFailed(Error?)
    case unknown
    
    var message: String {
        switch self {
        case .networkError:
            return "通信エラーが発生しました"
            
        case .decodeFailed(let error):
            return "デコードに失敗しました \(error.localizedDescription)"
        
        case .responceFailed(let error):
            return "レスポンスの取得に失敗しました \(error?.localizedDescription ?? "error")"
            
        case .unknown:
            return "不明なエラーが発生しました"
        }
    }
}

let networkErrorMessage = ApiError.networkError.message
let decodeErrorMessage = ApiError.decodeFailed(error).message
```

Errorからエラーメッセージを取得したい場合は、[localizedDescription](https://developer.apple.com/documentation/foundation/nserror/1414418-localizeddescription)プロパティを使う方法があります。

このプロパティは元々、NSErrorが定義しているプロパティなので、swiftのErrorが持っているプロパティではないのですが[Foundation](https://developer.apple.com/documentation/foundation)フレームワークをリンクしていると使えるようになります。

なぜ使えるのかというと、そもそも`Foundation`フレームワークはObjective-Cで作られており、Objective-CではFoundationフレームワークに含まれるNSErrorクラスを使ってエラーハンドリングしていたからです。

なので、swiftでも`FoundationをインポートすればNSErrorクラスが使える`ということです。


他にも`Int型`や`String型`なども引数に持たせることが可能で
`エラーに付随する情報`を表現することが出来ます。

```swift
enum DatabaseError {
    case networkError
    case invalidEntry(reson: String)
}

let invalidEntryReson = DatabaseError.invalidEntry(reson: "~が理由で無効です")
```

#おわり
ErrorとNSErrorの違いをまとめてみました。
swiftしか触ったことがないので、かなりswift寄りの記事になってしまいましたね。

もし、間違っている部分があればコメントして下さると有り難いです。

最後に下記にて参考資料を載せておきます。

#参考資料
[Swift3時代のErrorとNSErrorに関するいくつかの実験](https://qiita.com/kunichiko/items/e5405b9edbea8fed86d2#%E3%81%AF%E3%81%98%E3%82%81%E3%81%AB)
[Swift 4.0 エラー処理入門](https://qiita.com/koishi/items/67cf4d0f51c4d79f1d22)
[[Swift] Swiftのエラー処理についてざっくりとまとめてみた](https://dev.classmethod.jp/articles/about-error-handling/#toc-24)