Alamofire で APIClient を書いてみた
APIClient はどういう設計が良いのかあとずっと悩んでいたのですが、なんとなく形になったような気がしたので書いてみます。
Swift は型安全な言語なのですが、 API から取得される値は様々な型を取り得るので、それを汎用的に扱えるようにするためにはどうしたら良いなかあ、型の扱い方が難しいなあと感じました。
初心者なので鵜呑みにはしないでくださいな。
APIClient
サーバ側で 404 等のエラーが 発生した際は ["error": "エラーメッセージ"]
というレスポンスボディが返ってくることを想定しています。
Alamofire では、リクエストメソッドを Alamofire.Method で指定する必要があり、APIClient を使う Class を記述したファイル内で、毎回 import Alamofire
しなければならなそうで嫌だなあと思ったので、 Enum を使って Alamofire.Method に変換できるようにしました。
サーバ側でエラーが発生した際には、リクエスト自体には成功しているので response.result.isSuccess()
が true となります。
そのため、 response.result.isSuccess()
が false となるのはリクエストそのものに失敗しているときだと思うのですが、アプリ側では、サーバ側のエラーとリクエストそのものが失敗したときのエラーを同じエラーとして扱いたいなあと思ったので、 APIResponse という Struct を用意して、 response の値を APIResponse に変換するようにしてみました。
import Alamofire class APIClient { static let host = "http://localhost:3000/" static func httpRequest(method: RequestMethod, endpoint: String, parameters: [String: AnyObject]?, handler: (APIResponse) -> Void) { Alamofire.request(method.toAlamofile(), host + endpoint, parameters: parameters) .responseJSON { response in if response.result.isSuccess { handler(APIResponse(code: response.response!.statusCode, value: response.result.value!)) } else { handler(APIResponse(value: ["error": "通信に失敗しました"])) } } } } enum RequestMethod { case GET case POST case PUT case DELETE func toAlamofile() -> Alamofire.Method { switch self { case .GET: return .GET case .POST: return .POST case .PUT: return .PUT case .DELETE: return .DELETE } } } struct APIResponse { var status: APIStatus var value: AnyObject? var errorMessage: String? enum APIStatus { case Success case Failure func isSuccess() -> Bool { switch self { case .Success: return true case .Failure: return false } } } init(code: Int = 0, value: AnyObject) { switch code { case 200...299: status = APIStatus.Success self.value = value default: status = APIStatus.Failure self.errorMessage = value["error"] as? String } } }
使うとき
リクエスト先となるエンドポイントと、リクエストパラメータを用意します。
response には APIResponse のインスタンスが入ってきます。 response.status.isSuccess()
でリクエストに成功したかを判断し、成功 or 失敗 の処理を分けて記述します。
func test() { let endpoint = "accounts" // エンドポイント let parameters = ["page": 1] // リクエストパラメータ APIClient.httpRequest(.GET, endpoint: endpoint, parameters: parameters) { response in if response.status.isSuccess() { print(response.value!) // 通信に成功したときの処理 } else { print(response.errorMessage!) // 通信に失敗したときの処理 } } }