167
156

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 3 years have passed since last update.

RxSwift2.0.0でMVVMモデルなRSSFeedアプリを作ってみたまとめ

Last updated at Posted at 2016-01-08

:tada: 2016/04/02更新:RxSwiftでUIの更新にはDriverを使ってみた記事も書いてみました!

RxSwift

Reactive ProgrammingをSwiftで実現するライブラリ
Qiitaの記事も増えてきました!

便利そうだけど使いどころは・・・?
ということで、Swiftビギナーな自分がRxSwiftでMVVMモデルなRSSFeedアプリを作ってみたまとめ

どんなアプリ? :pushpin: :pushpin: :triangular_flag_on_post: :tada:

環境

  • Xcode 7.2
  • Swift 2.1
  • RxSwift 2.0.0
  • iOS8以上

リポジトリ

mafmoff/RxRSSFeed

##概要

IMG_6373.PNG

  • Sketch App Sourcesで配信しているRSS FeedをTableViewでリスト表示
  • リストを選択すると詳細をWebViewで表示
  • 取得するRSSはXMLなので、Google AJAX Feed APIを使ってJSONで取得

:tada: TIPS: 今回は練習用アプリなのでATSは無効化してあります

##使用ライブラリ

Library Use
Alamofire Swift用通信ライブラリ
ObjectMapper JSONのレスポンスをFeedクラスにマッピング
HTMLReader JSONに混在している HTMLタグをパース
RxSwift 今回の主役、ReactiveProgrammingをサポート
SDWebImage (Obj-C製)リストの画像ロードに使用
SVProgressHUD (Obj-C製)詳細画面のローディング表示

当初、めちゃいけてるPullRefreshライブラリDGElasticPullToRefreshを使う予定でしたが、AutoLayoutのバグで使用を断念

RxSwiftでのMVVMモデルの実現

こんなイメージ

スクリーンショット 2016-01-08 0.01.38.png

RxSwiftの使い所

1. Model : リクエストでObservableオブジェクトの生成

ここでは、APIリクエストでObservableオブジェクトを生成
レスポンスのJSONは、ObjectMapperを利用して作成したモデルクラスFeedResponseにマッピングしておくと楽でした

FeedRequest.swift
    func connect() -> Observable<FeedResponse> {
        let observable: Observable<FeedResponse> = Observable.create { (observer: AnyObserver<FeedResponse>) in
            self.request?.responseJSON(completionHandler: { response in
                print("\(response)")
                switch response.result {
                case .Success(let value):
                    guard let object = Mapper<FeedResponse>().map(value) else {
                        return observer.onCompleted()
                    }
                    observer.onNext(object)
                    observer.onCompleted()
                case .Failure(let error):
                    observer.onError(error)
                }
            })
            return AnonymousDisposable {
                
            }
        }
        return observable
    }

:tada: TIPS: func connect() -> Observable<FeedResponse>の部分をジェネリクスにすると、他のモデルクラスでも汎用的に使えます

ちなみに、この実装を実現しているライブラリがtaktem/TAKSwiftSupportです
こちらのRequestBaseを親としたリクエストクラスを作成すると便利です
taktemさんありがとうございます

:tada: TIPS: Observable.createRxSwift 2.0.0-beta以前ではcreateのみの記述でした

2. ViewModel : リクエスト呼び出し、レスポンスをVariable変数に格納

プロパティにはVariable型の変数を宣言しておく
このとき、Variable変数は初期化の際にも値の更新とみなされsendNextされてしまうので、意図しないSubscribeを避けるためfilteringできるように空もしくはnilにしておきます

ListViewModel.swift
    /// Request class
    private var request = FeedRequest()

    /// Feed
    private(set) var feed: Variable<Feed?> = Variable(nil)

    /// Entry
    private(set) var entries: Variable<[Entry]> = Variable([])

    /// Error
    private(set) var error: Variable<NSError?> = Variable(nil)

// 省略

func reloadData() {
        request.connect().subscribe(onNext: { [weak self] x in
            // Variableプロパティに格納
            self?.entries.value = x.feed.entries
            }, onError: { error in
                
            }, onCompleted: { () in
                
            }) { () in
                
        }.addDisposableTo(request.disposeBag)
    }

リクエストから受け取ったFeedResponse型のオブジェクトxから、TableViewに表示するデータソース用の配列entriesオブジェクトを取り出し、プロパティに代入し直す
この代入をトリガーにして、TableViewのリロードを行います

:tada: TIPS: Variableの変数は外部クラスから書き換えを禁止するためアクセスレベルをprivate(set) にしておくと安全です

3. ViewController : ViewModelのVariable変数をSubscribe 

ViewModelで宣言したVariableオブジェクトは.asObservable()Observableに変換できるので、ViewController上でSubscribeを行います
2.で代入した際の値の更新をトリガーに、TableViewのリロードを実施

ListController.swift

   func bind() {
        // Connection
        viewModel.entries.asObservable().filter { x in
            // 初期化の際にsubscribeをさけるためfilter
            // 配列の要素数が0件より多くなったときにsubscribeを実施
            return !x.isEmpty
            }.subscribe(onNext: { [unowned self] x in
                // 更新
                self.refresh.endRefreshing()
                self.tableView.reloadData()
                }, onError: { error in
                    // エラー
                    // FIXME: いつかやるかも
                }, onCompleted: { () in
                    // 完了
                }) { () in
                    
            }.addDisposableTo(disposeBag)
        

        // Pull Refresh
        refresh.rx_controlEvent(.ValueChanged).subscribeNext { [unowned self] x -> Void in
            // プルリフレッシュを購読し、ValueChangedがあった場合APIリロードを実行
            self.viewModel.reloadData()
        }.addDisposableTo(disposeBag)
    }
           

:tada: TIPS: .filterはViewModelでのentries変数の初期化(空配列の生成)のSubscribeを避けるために実施しています

:tada: TIPS: .asObservable()RxSwift 2.0.0-beta以前では不要でした(ちょっと面倒になった…?)

まとめ

  • 値が更新される度にbind部分の処理を通るので、プルリフレッシュ、インフィニットロードで再度API通信を行ったときなどもレスポンス後の処理が分散しない
  • MVVMモデルでViewControllerが肥大化せず可読性も上がるはず
     

以上です :tada:
Swift歴はまだまだ浅いので、さらによい実装方法を勉強させて頂きます!
ありがとうございました

167
156
2

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
167
156

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?