RxMoya + Codableで実現するリアクティブなAPIクライアント
Moya
最近MoyaというAPIクライアントを使っています。なかなか良さげで使い勝手も悪くありません。
github.com
Alamofireベースで作られており、Moyaを入れるだけでいい感じのAPIクライアントがすぐに手に入ります。
また、Rx拡張もされているのでRxSwiftなどとの相性も良いでしょう。
Codable
Swift4から使えるprotocolに「Codable」というのがあります。これを利用するとAPIから取得したデータ(JSON)のパースが非常に簡単に行なえます。
JSONとオブジェクトのマッピングにSwiftyJSONやObjectMapperなどのライブラリを使うことが多いですが、Codableを使えばもう必要ありません。
Moya + CodableでQiitaの情報を取得する
簡単な例としてQiitaの情報をMoyaとCodableを使って取得してみました。
GitHubにサンプルのプロジェクトをまるごと置いているので参考にしてください。
Codableに準拠したModelの定義
APIのレスポンスに従ってModelを作成します。structで定義してCodableに準拠させるだけ。
準拠させるといっても、内部で持つプロパティの型がCodableに準拠している場合は「Codable」を書いてあげるだけで済みます。
プロパティの型に独自のクラスが混ざっている場合は、そのクラスもCodableに準拠させる必要があります。
struct User: Codable { let description: String? let facebookID: String? let followeesCount: Int let followersCount: Int let githubLoginName: String? let id: String let itemsCount: Int let linkedinID: String? let location: String? let name: String let organization: String? let permanentID: Int let profileImageURL: String let twitterScreenName: String? let websiteURL: String? private enum CodingKeys: String, CodingKey { case description case facebookID = "facebook_id" case followeesCount = "followees_count" case followersCount = "followers_count" case githubLoginName = "github_login_name" case id case itemsCount = "items_count" case linkedinID = "linkedin_id" case location case name case organization case permanentID = "permanent_id" case profileImageURL = "profile_image_url" case twitterScreenName = "twitter_screen_name" case websiteURL = "website_url" } }
MoyaでAPIの定義
Moyaを使ってAPIの定義をします。Moyaではenumを使ってAPIを定義します。
そして「TargetType」というprotocolに準拠させることでリクエスト情報を明確にします。
プロジェクトの規模やAPI設計に応じて、enumの定義は慎重に行いましょう。case文が増えるとTargetTypeに準拠させる際に、switch文だらけになり見通しが悪くなってしまうことがあります。
enum UserAPI { case fetch } // MARK: - TargetType extension UserAPI: TargetType { var baseURL: URL { return URL(string: "https://qiita.com")! } var path: String { return "/api/v2/users" } var method: Moya.Method { return .get } var sampleData: Data { let path = Bundle.main.path(forResource: "sample", ofType: "json")! return FileHandle(forReadingAtPath: path)!.readDataToEndOfFile() } var task: Task { return .requestParameters(parameters: ["page":1], encoding: URLEncoding.default) } var headers: [String : String]? { return nil } }
だいたい読んで字のごとく、リクエスト先やメソッドタイプ、HTTPHeaderなどを定義します。
Taskは最近のアップデートで大幅に変更が加えられました。すでにMoyaを導入している方は要チェックです。
sampleDataでStubを作成
sampleDataに指定した情報をダミーのデータとして返すことができます。
APIの実装待ちや手元で任意のデータで確認したいときなどに便利ですよ。詳細の実装は後述。
MoyaProviderの作成
MoyaでAPI通信するためには「MoyaProvider」を作成する必要があります。
fileprivate let provider: MoyaProvider<UserAPI> = { let stubClosure = { (target: UserAPI) -> StubBehavior in return .never } let networkLoggerPlugin = NetworkLoggerPlugin(cURL: true) let plugins = [networkLoggerPlugin] return MoyaProvider<UserAPI>(stubClosure: stubClosure, plugins: plugins) }()
Genericsで先ほど作成した「UserAPI」を指定します。あとは必要に応じてStubやPluginの設定を行えば完成。
StubBehavior
TargetTypeで指定した「sampleData」はこのStubBehaviorで利用されます。
StubBehaviorには下記の3種類が用意されているので、場面に応じて使い分けてみましょう。
StubBehavior | 振る舞い |
---|---|
.never | 通常のAPI通信をする |
.immediate | sampleDataをすぐに返す |
.delay(x) | x秒後にsampleDataを返す |
指定した秒数分待ったあとにレスポンスを返すこともできるので、ローディングの様子や実際に近い動きを試せますね。
Plugin
プラグインは様々な種類がありますが、上記の例はAPI通信する際にcurlコマンドをログにはいてくれます。
$ curl -v \ -H "Accept-Language: en;q=1.0, ja-JP;q=0.9" \ -H "User-Agent: RxMoya+Codable/1.0 (com.imairi.RxMoya-Codable; build:1; iOS 11.0.0) Alamofire/4.5.1" \ -H "Accept-Encoding: gzip;q=1.0, compress;q=0.5" \ "https://qiita.com/api/v2/users?page=1"
MoyaはAlamofireベースで作られているので、上記のようなAlamofireにある便利機能も使えますよ。
他には、API通信を開始する直前やレスポンスを取得した直後などのイベントを取得できるプラグインもあります。プラグインは自分好みにカスタマイズして作成することも可能。
API通信
実際にMoyaProviderを利用してAPIから情報を取得してみます。
下記は「ボタンを押す→API通信→JSONをパース→その結果を返す」処理になります。
通常のMoyaを利用するとResult<Response, Error>型でレスポンスを受け取れますが、下記のようにRxMoyaを利用するとPrimitiveSequence<SingleTrait, Response>型で情報を受け取れます。ResponseはMoyaで定義されているAPIレスポンス用の型で、内部にレスポンスをData型で持っています。
let didFinishGetUsersSubject = PublishSubject<[User]>() input.tapButton .flatMap { self.provider.rx.request(UserAPI.fetch) } .flatMap({ (response) -> Observable<[User]> in return Observable<[User]>.create({ (observer) -> Disposable in do { let decoder = JSONDecoder() let users = try decoder.decode([User].self, from: response.data) observer.onNext(users) } catch(let error) { observer.onError(error) } return Disposables.create() }) }) .bind(to: didFinishGetUsersSubject) .disposed(by: disposeBag)
Codableに準拠したクラスにJSONをパースするにはJSONDecoderを利用しましょう。
レスポンスが配列の場合は上記のようにCodableに準拠したクラスをArray型で指定することでデコードできます。
このようにRxMoyaを利用すると、RxSwiftなどでリアクティブな振る舞いを定義している処理の中に、APIの処理をつなぎこみやすくなりますね。
おわりに
最近なんだかモヤモヤしているなという方はMoyaを使ってみてはいかがでしょうか。
github.com