(Swift2) Alamofire の upload 用 API クライアントを作成する
前回の記事で Alamofire の request メソッドに対する API クライアントを作成しました。
Swift2 Alamofire + ObjectMapper で API クライアントを作成する
この API クライアントを利用すると request は以下のように書くことができます。
API.call(Endpoint.User.Find(id: 1)) { response in switch response { case .Success(let result): print("success \(result)") case .Failure(let error): print("failure \(error)") } }
前回作成したのは request メソッドだけなので、今回は、前回作成した API クライアントを元に、画像等をアップロードする際に利用する upload メソッドのクライアントを作成したいと思います。
- Alamofire の upload 用のリクエストを定義する protocol を用意する( UploadProtocol.swift )
- upload を行う Endpoint を定義する ( Endpoint.swift )
- Alamofire の upload リクエストを行う class メソッドを実装する( API.swift )
以下のような形で画像のアップロードを行う API をコールすることができるようになります。
let image = UIImage(named: "test") let humanQuality: CGFloat = 0.8 let imageData = UIImageJPEGRepresentation(image, humanQuality) API.call(Endpoint.UploadPhoto(imageData: imageData)) { response in switch response { case .Success(let result): print("success \(result)") case .Failure(let error): print("failure \(error)") } }
1. Alamofire の upload 用のリクエストを定義する protocol を用意する( UploadProtocol.swift )
前回作成した RequestProtocol.swift を元に upload 用の UploadProtocol.swift を作成します。 upload と request で異なる点は以下の点になります
- 画像のアップロードでは multipart/form-data リクエストになる為 URLRequestConvertible は採用しない
- request では parameters 。 upload では multipartFormData を使う
RequestProtocol.swift を元に UploadProtocol.swift を作成すると以下のようになります。
・ UploadProtocol.swift
import Alamofire import ObjectMapper protocol UploadProtocol { // URLRequestConvertible は採用しない associatedtype ResponseType var baseURL: String { get } var method: Alamofire.Method { get } var path: String { get } var headers: [String: String]? { get } var multipartFormData: (MultipartFormData) -> () { get } // var parameters: [String: AnyObject]? { get } // upload では不要 // var encoding: ParameterEncoding { get } // upload では不要 func fromJson(json: AnyObject) -> Result<ResponseType, NSError> } extension UploadProtocol { var method: Alamofire.Method { return .POST // upload リクエストは基本 POST 処理なので、デフォルトで POST を宣言 } var baseURL: String { return "https://httpbin.org" } var headers: [String: String]? { return nil } var parameters: [String: AnyObject]? { return nil } var encoding: ParameterEncoding { return .URL } // upload では URLRequest ではなく multipartFormData を利用する // var URLRequest: NSMutableURLRequest { // let url = "\(baseURL)\(path)" // let encodedUrl = url.stringByAddingPercentEncodingWithAllowedCharacters( // NSCharacterSet.URLQueryAllowedCharacterSet()) // let mutableURLRequest = NSMutableURLRequest(URL: NSURL(string: encodedUrl!)!) // mutableURLRequest.HTTPMethod = method.rawValue // mutableURLRequest.allHTTPHeaderFields = headers // do { // if let parameters = parameters { // mutableURLRequest.HTTPBody = try NSJSONSerialization.dataWithJSONObject( // parameters, options: NSJSONWritingOptions()) // } // } catch { // // No-op // } // mutableURLRequest.setValue("application/json", forHTTPHeaderField: "Content-Type") // return mutableURLRequest // } 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) } } extension UploadProtocol where ResponseType: Mappable { func fromJson(json: AnyObject) -> Result<ResponseType, NSError> { guard let value = Mapper<ResponseType>().map(json) else { let errorInfo = [ NSLocalizedDescriptionKey: "Mapping object failed" , NSLocalizedRecoverySuggestionErrorKey: "Rainy days never stay." ] let error = NSError(domain: "com.example.app", code: 0, userInfo: errorInfo) return .Failure(error) } return .Success(value) } }
見てみると、ほとんど RequestProtocol.swift と同じなので RequestProtocol.swift と UploadProtorol.swfit の共通で採用する APIProtocol.swift を作成してリファクタリングします。最終的には以下のようなファイルになりました。
・ APIProtocol.swift
import Alamofire import ObjectMapper protocol APIProtocol { associatedtype ResponseType var baseURL: String { get } var method: Alamofire.Method { get } var path: String { get } var headers: [String: String]? { get } func fromJson(json: AnyObject) -> Result<ResponseType, NSError> } extension RequestProtocol { var method: Alamofire.Method { return .GET } var baseURL: String { return "https://httpbin.org" } var headers: [String: String]? { return nil } var encoding: ParameterEncoding { return .URL } 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) } } extension APIProtocol where ResponseType: Mappable { func fromJson(json: AnyObject) -> Result<ResponseType, NSError> { guard let value = Mapper<ResponseType>().map(json) else { let errorInfo = [ NSLocalizedDescriptionKey: "Mapping object failed" , NSLocalizedRecoverySuggestionErrorKey: "Rainy days never stay." ] let error = NSError(domain: "com.example.app", code: 0, userInfo: errorInfo) return .Failure(error) } return .Success(value) } }
・ RequestProtocol.swift
import Alamofire import ObjectMapper protocol RequestProtocol: APIProtocol, URLRequestConvertible { var parameters: [String: AnyObject]? { get } var encoding: ParameterEncoding { get } } extension RequestProtocol: APIProtocol { var parameters: [String: AnyObject]? { return nil } var encoding: ParameterEncoding { return .URL } var URLRequest: NSMutableURLRequest { let url = "\(baseURL)\(path)" let encodedUrl = url.stringByAddingPercentEncodingWithAllowedCharacters( NSCharacterSet.URLQueryAllowedCharacterSet()) let mutableURLRequest = NSMutableURLRequest(URL: NSURL(string: encodedUrl!)!) mutableURLRequest.HTTPMethod = method.rawValue mutableURLRequest.allHTTPHeaderFields = headers do { if let parameters = parameters { mutableURLRequest.HTTPBody = try NSJSONSerialization.dataWithJSONObject( parameters, options: NSJSONWritingOptions()) } } catch { // No-op } mutableURLRequest.setValue("application/json", forHTTPHeaderField: "Content-Type") return mutableURLRequest } }
・ UploadProtocol.swift
import Foundation import Alamofire protocol UploadProtocol: APIProtocol { var multipartFormData: (MultipartFormData) -> () { get } } extension UploadProtocol { var method: Alamofire.Method { return .POST } }
2. upload を行う Endpoint を定義する ( Endpoint.swift )
Request の時と同じように Endpoint.swift 内に定義します。
class Endpoint { class UploadPhoto: UploadProtocol { typealias ResponseType = AnyObject var imageData: NSData init(imageData: NSData) { self.imageData = imageData } var path: String { return "/post" } // クロージャー内で self を利用する為に lazy を指定する // クロージャー内で self を参照すると強参照で循環してしまうので、weak self としてクロージャー内の self の参照を弱参照にする lazy var multipartFormData: (MultipartFormData) -> () = { [weak self](data: MultipartFormData) in guard let weakSelf = self else { return } data.appendBodyPart( data: weakSelf.imageData, name: "photo", fileName: "image", mimeType: "image/jpeg" ) } } }
3. Alamofire の upload リクエストを行う class メソッドを実装する( API.swift )
API.swift に call メソッドを引数の異なる同名メソッドで定義します
・ API.swift
// request 用の call メソッド class func call<T: RequestProtocol, V where T.ResponseType == V>(request: T, completion: (Result<V, NSError>) -> Void) -> Request? { ... } class func call<T: UploadProtocol, V where T.ResponseType == V>(request: T, completion: (Result<V, NSError>) -> Void){ Alamofire.upload( request.method, request.baseURL + request.path, headers: request.headers, multipartFormData: request.multipartFormData, encodingCompletion: { encodingResult in switch encodingResult { case .Success(let upload, _, _): upload.responseJSON { response in switch response.result { case .Success(let json): completion(request.fromJson(json) case .Failure(let error): completion(.Failure(error)) } } case .Failure(let encodingError): print(encodingError) } } ) }
これで以下のように request の時と同じように upload を行うことができるようになります。
let image = UIImage(named: "test") let humanQuality: CGFloat = 0.8 let imageData = UIImageJPEGRepresentation(image, humanQuality) API.call(Endpoint.UploadPhoto(imageData: imageData)) { response in switch response { case .Success(let result): print("success \(result)") case .Failure(let error): print("failure \(error)") } }