こんにちは。エンジニアの浅井です。 普段はマネーフォワードのiOSアプリ開発を担当しています。
弊社では、全社で使用する共通ライブラリの開発を積極的に行っています。 今回は、先日正式リリースされたSwift2.0で作成中のAPI通信基盤をご紹介します。
APIKitを参考に、下記のライブラリを用いて実装しています。 * Alamofire(HTTP通信) * ObjectMapper(JSONのパース)
本エントリーの肝
本エントリーではSwift2.0で追加された、ProtocolExtension
を利用しています。
プロトコルに実装を定義できるだけでなく、細かく条件指定ができるので、そのプロトコルに準拠したクラスの実装を少なく抑えることができます。
また、typealias
によるジェネリクスを利用することで、タイプセーフな設計になっています。
利用側のコード
兎にも角にもまずは利用側のコードです。
API.call(Endpoint.GetUsers()) { result in switch result { case .Success(let users): // 成功時の処理 println("success") case .Failure(let data, let error): // 失敗時の処理 println("failure") } }
API.call
をリクエストオブジェクトを引数にして呼び出す(後述RequestProtocol
)- 返却される
result
はAlamofire.Result
型 - Success時の
users
はジェネリクスにより型が明らかになっている(後述ResponseType
)
APIの例
例えばこんなAPIがあったとします。
URL
https://api.test.com/users
Response
{ "users": [ { id: 1, name: "yuki" }, { id: 2, name: "asai" } ] }
こんなときObjectMapperを用いたモデルクラスは以下のように定義するでしょう。
class Users: Mappalbe { var users: [User]? required init?(_ map: Map) { mapping(map) } func mapping(map: Map) { users <- map["users"] } } class User: Mappable { var id: Int? var name: String? required init?(_ map: Map) { mapping(map) } func mapping(map: Map) { id <- map["id"] name <- map["name"] } }
そしてリクエストオブジェクトは以下のように定義します。
class Endpoint { class GetUsers: RequestProtocol { typealias ResponseType = Users var baseUrl: String { return "https://api.test.com/" } var path: String { return "users" } } }
このResponseType
が成功時のresultの値の型になります。
ライブラリ側のコード
APIの定義
Alamofireを用いることで、HTTP通信の処理もすっきりしています。
class API { class func call<T: RequestProtocol, V where T.ResponseType == V>(request: T, completion: (Result<V>) -> Void) { Alamofire.request(request) .responseJSON { req, res, result in switch result { case .Success(let json): completion(request.fromJson(json)) case .Failure(let data, let error): completion(.Failure(data, error)) } } } }
RequestProtocolの定義
protocol RequestProtocol: URLRequestConvertible { typealias ResponseType var method: Method { get } var baseUrl: String { get } var path: String { get } var parameters: [String: AnyObject]? { get } var encoding: ParameterEncoding { get } var headers: [String: String]? { get } func fromJson(json: AnyObject) -> Result<ResponseType> } extension RequestProtocol { var method: Method { return .GET } var parameters: [String: AnyObject]? { return nil } var encoding: ParameterEncoding { return .URL } var headers: [String: String]? { return nil } func fromJson(json: AnyObject) -> Result<ResponseType> { guard let value = json as? ResponseType else { return .Failure(nil, error(0, localizedDescription: "Convert object failed")) } return .Success(value) } }
ObjectMapper対応
ResponseTypeがObjectMapper.Mappalbe
に準拠したクラスでであれば、ObjectMapperのマッピング処理を行うよう、ProtocolExtensionで定義します。
extension RequestProtocol where ResponseType: Mappable { func fromJson(json: AnyObject) -> Result<ResponseType> { guard let value = Mapper<ResponseType>().map(json) else { return .Failure(nil, error(0, localizedDescription: "Mapping object failed")) } return .Success(value) } }
JSONをそのまま利用したい場合はRequestProtocolを準拠したクラスでfromJsonを都度実装してやればよいですし、ProtocolExtensionで拡張していくことで、様々なマッパーに対応させることができるでしょう。
最後に
マネーフォワードでは、新しい技術も積極的に取り入れていくエンジニアを募集しています。 ご応募お待ちしております!
【採用サイト】 ■『マネーフォワード採用サイト』 https://recruit.moneyforward.com/ ■『Wantedly』 https://www.wantedly.com/companies/moneyforward
【公開カレンダー】 ■マネーフォワード公開カレンダー
【プロダクト一覧】 ■家計簿アプリ・クラウド家計簿ソフト『マネーフォワード』 https://moneyforward.com/ ■家計簿アプリ・クラウド家計簿ソフト『マネーフォワード』 iPhone,iPad ■家計簿アプリ・クラウド家計簿ソフト『マネーフォワード』 Android ■クラウド型会計ソフト『MFクラウド会計』 https://biz.moneyforward.com/ ■クラウド型請求書管理ソフト『MFクラウド請求書』 https://invoice.moneyforward.com/ ■クラウド型給与計算ソフト『MFクラウド給与』 https://payroll.moneyforward.com/ ■消込ソフト・システム『MFクラウド消込』 https://biz.moneyforward.com/reconciliation/ ■マイナンバー対応『MFクラウドマイナンバー』 https://biz.moneyforward.com/mynumber