Swift2 - Alamofire のリクエストでネットワークエラーを検知する
swift2.2, Xcode7
API 通信するアプリを開発していると、ネットワークエラーを検知して、アラートを出すといった処理は共通して書きたいものです。
Apple がサンプルとして公開している Reachability を Swift に変換して使うのもアリですが、ネットワーク通信の ON / OFF だけをサクッと検知したいのであれば Alamofire の NSError を確認することで、簡単に判別することができます。
- ネットワークエラーを Alamofire の error( NSError 型 ) から補足する
- アラートにそのまま表示できるように NSError を enum( NSErrorType ) で生成する
- Alamofire の error を NSErrorType に変換する
- NSError をアラート表示する
1. ネットワークエラーを Alamofire の error( NSError 型 ) から補足する
インターネット接続が OFF の状態で Alamofire のリクエストを投げると error.code が NSURLErrorNotConnectedToInternet となるので
if error.code == NSURLErrorNotConnectedToInternet { // インターネット接続 OFF }
で、検知できます。
2. アラートにそのまま表示できるように NSError を enum で生成する
インタネット接続 OFF の時に画面にアラートを表示する為、 NSError を作成する enum を準備します。
enum NSErrorType { case System case NetworkOff var descriptionKey: String { switch self { case .System: return "エラー" case .NetworkOff: return "ネットワークに接続できません" } } var recoverySuggestionErrorKey: String { switch self { case .System, .NetworkOff: return "接続環境の良いところで再度お試しください" } } var error: NSError { let errorInfo = [ NSLocalizedDescriptionKey: self.descriptionKey, NSLocalizedRecoverySuggestionErrorKey: self.recoverySuggestionErrorKey ] let error = NSError(domain: "com.example.com", code: self.code, userInfo: errorInfo) return error } }
3. Alamofire の error を NSErrorType に変換する
前回の記事で作成した API クライアントを拡張します。API クライアントは以下のようになっています。
import Alamofire class API { class func call<T: RequestProtocol, V where T.ResponseType == V>(request: T, completion: (Result<V, NSError>) -> Void) { Alamofire.request(request) .validate(contentType: ["application/json"]) .responseJSON { response in switch response.result { case .Success(let json): completion(request.fromJson(json)) case .Failure(let error): completion(.Failure(error)) } } } }
今回はリクエスト失敗時の処理なので .Failure を以下のように書き換えます。
case .Failure(let error): completion(.Failure(request.fromNSError(error))) }
そして、RequestProtocol.swift に fromNSError メソッドを加えます。
protocol RequestProtocol: URLRequestConvertible { associatedtype ResponseType ... 省略 func fromJson(json: AnyObject) -> Result<ResponseType, NSError> func fromNSError(error: NSError) -> NSError } extension RequestProtocol { var method: Alamofire.Method { return .GET } ... 省略 func fromJson(json: AnyObject) -> Result<ResponseType, NSError> { guard let value = json as? ResponseType else { let errorInfo = [NSLocalizedDescriptionKey: "Convert object failed" , NSLocalizedRecoverySuggestionErrorKey: "Good luck!"] let error = NSError(domain: "com.example.app", code: 0, userInfo: errorInfo) return .Failure(error) } return .Success(value) } func fromNSError(error: NSError) -> NSError { if error.code == NSURLErrorNotConnectedToInternet { return NSErrorType.NetworkOff.error } else { return NSErrorType.System.error } } }
これで、ネットワークオフの時は NSErrorType.NetworkOff.error が、その他のエラーの時は NSErrorType.System.error が返却されるようになりました。
4. NSError をアラート表示する
色んなところで使い回せるように NSError を渡したら Alert を表示する Utility を作っておきます。
struct AlertUtility { static func showErrorAlert(error: NSError?, viewController: UIViewController){ guard let error = error else { return } let title: String = error.localizedDescription let message: String = error.localizedRecoverySuggestion ?? "接続環境の良いところで再度お試しください" self.showErrorAlert(title, message: message, viewController: viewController) } }
あとは API.call の .Failure の error をそのまま表示してあげるだけで、ネットワークオフの時のエラーを表示できるようになります。本来は ViewController で API.call するのは良くないですが、今回は説明を簡単にする為に ViewController で呼んでいます。
ViewController.swift
override func viewDidAppear(animated: Bool) { super.viewDidAppear(animated) API.call(Endpoint.User.Find(id: 1)) { response in switch response { case .Success(let result): print("success \(result)") case .Failure(let error): AlertUtility.showErrorAlert(error, viewController: self) } } }