Alamofireで204 No ContentなAPIのレスポンスをどう扱うか
TL;DR
現状、SwiftのHTTPクライアントであるAlamofireで204 No ContentなAPIの処理のベストプラクティスは存在しないので、response
で受けるか、Response Serializerを作るか、自分でResponseを作って泥臭くやるしかない。
Response Serializerを作るのが、他のAPIの処理と同じように実装できていいように僕は思う。
背景
Swift 2.0とiOS 9対応をしていて、Alamofireのバージョンが上がったことで、AlamofireがResponse(Struct)とResult(Enum)を持つようになった。
それに伴い、responseJSON
やresponseString
などはデータが返ってくることを期待した実装に変更され、通信完了時のハンドラにResultを含むResponseが渡されるようになった。
(以前、使っていたバージョンではそういったものはなく、Request, Response, Data, ErrorがOptionalで渡される実装だったので、自前でResponseを定義して扱っていた。)
// 以前: ハンドラにrequest, response, json, errorのOptionalが渡されている Alamofire.request(urlRequest) .validate(statusCode: 200..<300) .responseJSON { (request, response, json, error) in // code } // 現在: ハンドラにResponse<AnyObject, NSError>が渡される Alamofire.request(urlRequest) .validate(statusCode: 200..<300) .responseJSON { response in // code }
response
以外の実装ではデータが返ってくることを期待しているので、204 No Contentの場合にシリアライズエラーとなって処理が失敗したことになってしまう。
対策
対策は幾つか思いつく。
response
はresponseJSON
やresponseString
から呼び出されるメソッドで、通信完了時に呼び出されるハンドラにRequest, Response, Data, ErrorがOptionalで返ってくるので、他のAPIとは別の方法でデータを処理するresponse
とAlamofireのResponse、Resultを組み合わせて、他のAPIと同じ処理ができるようにする
けど上記の方法はいずれもアドホックな対応になり、微妙な気がしてやめた。
Issueを巡っていると、コミッタの人が例えばNo Dataを扱うResponse Serializerなんかあったらどうかみたいな話をしていて、Response Serializer作れることが分かった。
Response Serializerを作るとresponseJSON
と同じようにレスポンスを扱えるので便利そうだし、作るぞ!!という気持ちになったので作った。
実装
ドキュメントにXMLのResponse Serializerのサンプルがあったので、これを参考にした。
https://github.com/Alamofire/Alamofire#creating-a-custom-response-serializer
extension Request { public static func NoContentResponseSerializer() -> ResponseSerializer<AnyObject, NSError> { return ResponseSerializer { request, response, data, error in guard error == nil else { return .Failure(error!) } guard let _ = data else { let failureReason = "Data should not exist. Input data should nil." let error = Error.errorWithCode(.DataSerializationFailed, failureReason: failureReason) return .Failure(error) } return .Success("") } } public func responseNoContent(completionHandler: Response<AnyObject, NSError> -> Void) -> Self { return response(responseSerializer: Request.NoContentResponseSerializer(), completionHandler: completionHandler) } }
.Success("")
で空文字を返してるのはちょっとした理由があって、responseJSON
がResponse<AnyObject, NSError>
を返してくるので、そこを揃えたかったため。
本来であればnil
を入れて返したいところで、もうちょっとうまい実装があればOptionalとして返したりできそうだが、OptionalとAnyObjectを同じように扱うにはAnyを使う必要があって、現時点ではSwfit 2.0とiOS 9対応を優先させて、とりあえずこれで動くようにしてる。
あとは呼び出し側で、
Alamofire.request(urlRequest) .validate(statusCode: [204]) .responseNoContent { response in // code }
のように呼び出せば良い。
まとめ
他にいい方法があったり、こんなやり方イケてないだろって話があれば、ぜひ教えていただきたく、何卒。 AnyObjectは割とよく使うけど、Anyって結構使うもんなんだろうか…。