koogawa blog

iOS、Android、foursquareに関する話題

RxSwiftを使ってfoursquareのベニューを取得するメモ(Swift 3.0版)

以前書いたこちらの記事がさっそくビルドできなくなっていたので、Swift 3.0 対応版としてリライトしました。

blog.koogawa.com


RxSwift のメリットを理解するには実際に使ってみるのが一番!ということで、とりあえず foursquare のベニューを取得するサンプルを作ってみました。

f:id:koogawa:20160515223640p:plain

※RxSwift は絶賛勉強中なので間違ったことを書いている可能性が高いです。ツッコミ歓迎です!

動作環境

  • Swift 3.0
  • RxCocoa (3.0.0-rc.1):
  • RxSwift (3.0.0-rc.1)
  • SwiftyJSON (3.1.1)
  • Xcode 8.0

APIクライアント

APIにアクセスする部分を作ります。戻り値を Observable<[Venue]> にしているのがポイントです。

func search(query: String = "") -> Observable<[Venue]> {
    return Observable.create{ observer in
        let client = FoursquareAPIClient(accessToken: "YOUR_TOKEN")
        let parameter: [String: String] = [
            "ll": "40.7,-74",
            "query": query
        ];
        client.request(path: "venues/search", parameter: parameter) {
            [weak self] data, error in
            guard let strongSelf = self, let data = data else { return }
            let json = JSON(data: data)
            let venues = strongSelf.parse(venuesJSON: json["response"]["venues"])
            observer.on(.next(venues))
            observer.on(.completed)
        }
        return Disposables.create {}
    }
}

fileprivate func parse(venuesJSON: JSON) -> [Venue] {
    var venues = [Venue]()
    for (key: _, venueJSON: JSON) in venuesJSON {
        venues.append(Venue(json: JSON))
    }
    return venues
}

通信ライブラリは拙作 FoursquareAPIClient を使用しました。

ViewModel

ViewModel から先ほどの serch を呼び出す部分です。

public func fetch(query: String = "") {
    client.search(query: query)
        .subscribe { [weak self] result in
            switch result {
            case .next(let value):
                self?.venues.value = value
            case .error(let error):
                print(error)
            case .completed:
                ()
            }
        }
        .addDisposableTo(disposeBag)
}

search の戻り値が Observable<[Venue]> なので、これを subscribe することができます。その中で、返ってくる値(ベニューリストなど)を Variable 型の venues にセットしています。この venues は ViewController から bind されるため、値が変更されるタイミングで何か処理を発火させることができるわけです。

ViewController

ViewController は30行程と、かなりスッキリしました。rx.items(dataSource: ) でベニューリストと TableView を bind しているのがポイントです。ViewModel のベニューリストに変化があるとTableViewがリロードされます。

import UIKit
import RxSwift
import RxCocoa

class ViewController: UIViewController {

    @IBOutlet weak var tableView: UITableView!
    
    var viewModel = ViewModel()
    var dataSource = DataSource()
    var delegate = Delegate()
    let disposeBag = DisposeBag()

    override func viewDidLoad() {
        super.viewDidLoad()
        self.tableView.delegate = self.delegate
        self.viewModel.fetch()

        self.viewModel.venues
            .asDriver()
            .drive (
                self.tableView.rx.items(dataSource: self.dataSource)
            )
            .addDisposableTo(self.disposeBag)
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
}

完全なソースコードはこちらです。

GitHub - koogawa/RxSwiftSample: RxSwiftSample

参考にさせて頂いた記事など