2
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Alamofire + RxSwiftを利用したシンプルなアーキテクチャ実装のサンプル

Last updated at Posted at 2018-06-19

はじめに

今回はAlamofireとRxSwiftを利用して、ネットワーク経由でデータ取得をする際に、よく必要となる下記の処理を含めたシンプルなアーキテクチャでの実装例をご紹介します。

  • データ取得中はIndicatorを表示させる(下記左側)
  • エラーが発生した場合はバナーを表示させる(下記右側)

Simulator Screen Shot - iPhone 8 Plus - 2018-06-19 at 12.41.07.png  Simulator Screen Shot - iPhone 8 Plus - 2018-06-19 at 12.41.38.png

アーキテクチャ

アーキテクチャは下記のような構成にしています。

スクリーンショット 2018-06-19 10.36.47.png

  • 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://github.com/alyousecond/AlamofireExample

git cloneしたあとcarthage updateしてください。

$ git clone https://github.com/alyousecond/AlamofireExample.git
$ cd AlamofireExample
$ carthage update --platform ios
2
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?