はじめに
今回はAlamofireとRxSwiftを利用して、ネットワーク経由でデータ取得をする際に、よく必要となる下記の処理を含めたシンプルなアーキテクチャでの実装例をご紹介します。
- データ取得中はIndicatorを表示させる(下記左側)
- エラーが発生した場合はバナーを表示させる(下記右側)
アーキテクチャ
アーキテクチャは下記のような構成にしています。
- ViewからのEventやRepositoryからのデータ更新はすべてSimpleStateに反映(図のEvent Call)されます。
- 各Viewの要素はSimpleStateにバインディングされており、SimpleStateの変化に応じて各要素の状態が変化します。
- 左側から右側にリクエストが送信されますが、一方通行のため戻り値にデータはありません。
データがない、もしくはObservableを(Repository -> Presenter部分)返します。 - オブジェクト間の依存関係は相互参照、循環参照しないように右側に向かっての参照のみ保持しています。
今回のサンプルで利用しているサービス・ライブラリのご紹介
httpbin http://httpbin.org
サーバー側のサービスとしてこちらのサービスを利用させていただいております。http://httpbin.org/ip を利用しています。
リクエストするとリクエスト元のIPが下記のように返ってきます。
{"origin":"XXX.0.3.50"}
Alamofire https://github.com/Alamofire/Alamofire
Swiftで書かれたHTTPネットワーキングライブラリ
RxSwift https://github.com/ReactiveX/RxSwift
Swiftでリアクティブプログラミングをするためにライブラリ
SVProgressHUD https://github.com/SVProgressHUD/SVProgressHUD
クリーンで軽量な進行状況を表示するためのライブラリ
ObjectMapper https://github.com/Hearst-DD/ObjectMapper
Swiftで書かれたシンプルなJSONオブジェクトマッピングライブラリ
NotificationBanner https://github.com/Daltron/NotificationBanner
高度にカスタマイズ可能なアプリ通知バナーを表示するためのライブラリ
エラー時のバナー表示に利用
下記はNotificationBannerに依存しているライブラリになります。
SnapKit https://github.com/SnapKit/SnapKit
iOSとOS X用のInterface Builderを使わずにAutoLayoutをDSL風に記述できるライブラリ
Intreface Builderを利用しない開発には必須のアイテムかと思います。
MarqueeLabel https://github.com/cbpowell/MarqueeLabel
ラベルのテキストが指定されたフレーム内に収まらないときにスクロールするマーキー効果を自動的に追加するライブラリ
作成したオブジェクトの説明
HttpRequestable(Protocol) (ファイル名:Repository+Requestable.swift)
Alamofireを利用して、JSONデータをMappableに準拠したEntityに変換します。今回は変換するだけの処理なので共通化してProtocol + Extensionで定義してあります。
SimpleRepository(Struct)
Repositoryの実態ではありますが、HttpRequestableに準拠させているため処理のコードはありません。
struct SimpleRepository: HttpRequestable {}
Entityに応じた加工処理を行う場合には個別に実装することになります。
APIManager(class)
Alamofireでリクエストする際のオプションを設定しています。
今回はリクエストタイムアウトを5秒に設定し、エラー時の確認が早くできるようにしてあります。
これはシングルトンのためClassで実装。
AppError(Enum)
ネットワークのエラー以外でも、エラーを包括的に管理するためにErrorプロトコルを拡張。
独自のオブジェクトはEquatableプロトコルに準拠にしておくと、監視する際にObservableからdistinctUntilChanged()
で重複要素を排除できるようになるので、都度処理を記述しなくても済みます。
Equatableプロトコル
static func ==(lhs: AppError, rhs: AppError) -> Bool {
return lhs.message == rhs.message
}
HttpbinIpEntity(Struct)
Mappableプロトコルに準拠。取得したJSONデータをオブジェクトにマッピングするための加工処理を行います。
主たるコードは下記部分に実装。
mutating func mapping(map: Map) {
origin<-map["origin"]
}
SimpleState(Struct)
Viewの状態を保持するための構造体になります。
更新はSimpleSate全体へ行います。(書き換え)
各要素の目的は下記のとおりです。
- isFetching:データ取得・停止の状態
- error:エラーの状態
- origin:取得したIPアドレス
SimplePresenter(Struct)
UseCase、Presenter、Gatewayなどの処理をまとめたものになります。
SimpleSateをBehaviorSubjectとして外部に公開。
リクエスト時、レスポンス時それぞれの状態に応じてSimpleSateを更新してイベントを発行。
SimpleRepositoryへの参照は、テストやスケルトンによる代替えをしやすくするために実態ではなく、HttpRequestable(プロトコル)としています。
SimpleViewController(class)
今回は単純にpresenterへの参照を設定。(SimplePresenterのSimpleRepositoryへの参照と同様にして、コンストラクタで設定する方法やインスタンスを作成した後に注入する方法のほうがベター)
presenterで保持しているViewStateのObservableをメインスレッドで処理するように設定。
let viewState = presenter.stateVariable
.asObservable()
.observeOn(MainScheduler.instance)
ViewStateで保持している各要素をバインド(もしくはサブスクライブ)。
viewState
.map{ $0.isFetching }
.distinctUntilChanged()
.subscribe(onNext: { $0 ? SVProgressHUD.show() : SVProgressHUD.dismiss() })
.disposed(by: disposeBag)
viewState
.map{ $0.isFetching }
.distinctUntilChanged()
.bind(to: UIApplication.shared.rx.isNetworkActivityIndicatorVisible)
.disposed(by: disposeBag)
viewState
.map { $0.error }
.distinctUntilChanged()
.subscribe(onNext: { [weak self] appError in self?.switchNotificationBanner(error: appError) })
.disposed(by: disposeBag)
viewState
.map { $0.origin }
.distinctUntilChanged()
.bind(to: ipLabel.rx.text)
.disposed(by: disposeBag)
origin以外はProtocol+Extensionとして共通化できそうですが、今回のサンプルでは割愛します。
以上になります。
SwiftらしくProtocol、Extension、Enum、Struct、ジェネリクス、そしてリアクティブプログラミングなどを総動員したサンプルコードになっています。
それぞれの詳細な情報はQiitaにもたくさんあると思いますが、それらをまとめて利用したケースとして何かの参考になれば幸いです。
参考にさせていただいた情報ソース
Alamofire
-
https://dev.classmethod.jp/smartphone/use-rxswift-for-http-networking/
RxSwift - https://qiita.com/k5n/items/17f845a75cce6b737d1e
ソースコード
完成したソースは下記にあります。
https://github.com/alyousecond/AlamofireExample
git clone
したあとcarthage update
してください。
$ git clone https://github.com/alyousecond/AlamofireExample.git
$ cd AlamofireExample
$ carthage update --platform ios