8
9

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 1 year has passed since last update.

RxSwift入門に丁度いいサンプルコード書いてみた

Last updated at Posted at 2022-01-24

はじめに

最近、少しずつRxSwiftの勉強をしています。
僕が勉強始めた時、いい塩梅のサンプルコードを探すのに、少しだけ時間がかかってしまったので、自分が書いたサンプルコードを記事にしようと思いました。

これからRxSwiftの門を叩く方のお役に立てれば幸いです。

前提条件

  • Swiftが読めること
  • Codableを利用したAPIリクエストについて理解していること
  • MVVMの特徴を理解していること
  • RxSwiftが概要レベルで分かること

サンプルコードの概要

アプリを起動すると、Qiitaの記事一覧の検索APIをコールして、そのレスポンスをTableViewに表示するだけのアプリです。

RxSwiftで動く簡単なアプリを第一目標にしたので、

  • Protocolで抽象を参照するなどのリファクタリングはしてません。
  • エラーハンドリングとか適当です。

↑ご容赦ください。

コード&解説

RxSwiftを利用するからには、データバインディングを活用したMVVMアーキテクチャを採用したいということで、今回は、MVVMで実装しました。

Model

QiitaAPIで表示したいデータをCodableを使ってデコードできるようにしたモデルクラスを定義ます。

QiitaArticleModel.swift
// MARK: - QiitaArticleModel
struct QiitaArticleModel: Codable {
    let title: String
    let url: String
    let user: User
}

// MARK: - User
struct User: Codable {
    let id: String
    let profileImageURL: String

    enum CodingKeys: String, CodingKey {
        case id
        case profileImageURL = "profile_image_url"
    }
}

ViewModel

最初は知らなくて進めてたのですが、RxSwfitを導入することで、URLSessionを使ったAPI通信もこんな感じで書けるようです。

まだ完全にキャッチアップしきれてませんが、熟練度の高いサンプルコードを見ると、もう少し異なる書き方をされていたりするので、まだまだ奥が深そうです。。。

これ使いこなせたらAPI通信が相当お手軽ですね。
→ 通信部分の共通化とかやってみたい。

QiitaArticleViewModel.swift
import Foundation
import RxSwift
import RxCocoa

class QiitaArticleViewModel {
    private var disposeBag = DisposeBag()
    
    var articles = BehaviorRelay<[QiitaArticleModel]>(value: [])
    
    func requestQiitaArticle() {
        guard let url = URL(string: "https://qiita.com/api/v2/items?page=1&per_page=20") else { return }
        let urlRequest = URLRequest(url: url)
        
        URLSession.shared.rx.response(request: urlRequest)
            .subscribe { [weak self] response, data in
                
                guard let articles = try? JSONDecoder().decode([QiitaArticleModel].self, from: data) else { return }
                
                self?.articles.accept(articles)
                
            } onError: { error in
                print(error.localizedDescription)
            }
            .disposed(by: disposeBag)
    }
}

View(ViewController)

個人的に生のSwiftしか書いてこなかった人間が一番戸惑ったのは、TableViewのところです。
RxSwiftを使わない場合、DataSourceの準拠が漏れると、データが表示されなかったりしますが、RxSwiftを使えば、DataSourceに準拠しなくてもいいみたいです。

調べたら、TableViewにデータを表示するための書き方は何通りかあるみたいです。
RxSwfit入門者であれば、このやり方が一番、シンプルなのかなぁとは個人的に思います。

QiitaArticleViewController.swift
import UIKit
import RxSwift

class QiitaArticleViewController: UIViewController {
    
    private var viewModel = QiitaArticleViewModel()
    private var disposeBag = DisposeBag()
    
    @IBOutlet private weak var tableView: UITableView! {
        didSet {
            tableView.register(UINib(nibName: QiitaArticleCell.identifier, bundle: nil), forCellReuseIdentifier: QiitaArticleCell.identifier)
        }
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        
        bind()
    }
    
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
                
        viewModel.requestQiitaArticle()
    }
    
    private func bind() {
        viewModel.articles
            .bind(to: tableView.rx.items(cellIdentifier: QiitaArticleCell.identifier, cellType: QiitaArticleCell.self)) { row, element, cell in
                cell.configureCell(model: element, row: row)
            }
            .disposed(by: disposeBag)

    }
}

アプリの動作全体の流れとしては、

  1. 画面が開かれた時にリクエストを投げるように、ViewからViewModelに依頼する ←イベントの発火
  2. ViewModelでリクエストの処理結果を観察して、レスポンスを待つ。
  3. レスポンスが来たらacceptを使って、Relay に.nextイベントを送信する
  4. articlesはTableViewとデータバインディングされているので、テーブルを更新する処理が走る。

といった感じだと思います。

個人的に、RxSwiftを用いたMVVMアーキテクチャは、
開発者が仕様書に沿って引いた導線(Observerとバインディング)に着火(ユーザーイベント)されたらゴールへ着地する(処理が流れる)というイメージで勝手に捉えています。

最後に、RxSwift無関係ですが、カスタムCellのコードも貼っておきます。

QiitaArticleCell.swift
import UIKit

class QiitaArticleCell: UITableViewCell {
    
    static var identifier = "QiitaArticleCell"
    
    @IBOutlet private weak var userNameLabel: UILabel!
    @IBOutlet private weak var titleLabel: UILabel!
    
    override func awakeFromNib() {
        super.awakeFromNib()
        // Initialization code
    }

    override func setSelected(_ selected: Bool, animated: Bool) {
        super.setSelected(selected, animated: animated)

        // Configure the view for the selected state
    }
    
    func configureCell(model: QiitaArticleModel, row: Int) {
        userNameLabel.text = model.user.id
        titleLabel.text = model.title
        self.contentView.backgroundColor = (row % 2 == 0) ? .black : .gray
    }
    
}

おわりに

とりあえず、サンプルアプリ1つできたので、今度は、「検索機能」でも追加してみようと思います。

参考にさせていただいた記事・サンプル

8
9
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
8
9

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?