iOS
翻訳
Swift
RxSwift

[翻訳] なぜRxSwiftを使うのか? (RxSwift-Chinese-Documentation)

中国語で書かれたRxSwiftのドキュメントが非常に参考になりました。
他の方のRxSwiftの勉強に少しでも役に立てばと思い、日本語に翻訳してみました!
今回は1章の「なぜRxSwiftを使うのか?」となります。

原文: https://beeth0ven.github.io/RxSwift-Chinese-Documentation/content/why_rxswift.html

Target Action

従来のやり方

button.addTarget(self, action: #selector(buttonTapped), for: .touchUpInside)
func buttonTapped() {
    print("button Tapped")
}

Rxでやるなら

button.rx.tap
    .subscribe(onNext: {
        print("button Tapped")
    })
    .disposed(by: disposeBag)

Target Actionではなくこちらを使えばコードのロジックがはっきりと見えるようになります。

Delegate

従来のやり方

class ViewController: UIViewController {
    ...
    override func viewDidLoad() {
        super.viewDidLoad()
        scrollView.delegate = self
    }
}

extension ViewController: UIScrollViewDelegate {
    func scrollViewDidScroll(_ scrollView: UIScrollView) {
        print("contentOffset: \(scrollView.contentOffset)")
    }
}

Rxでやるなら

class ViewController: UIViewController {
    ...
    override func viewDidLoad() {
        super.viewDidLoad()

        scrollView.rx.contentOffset
            .subscribe(onNext: { contentOffset in
                print("contentOffset: \(contentOffset)")
            })
            .disposed(by: disposeBag)
    }
}

これで望む結果が取れるので、Delegateを書く必要はありません。

Closure コールバック

従来のやり方

URLSession.shared.dataTask(with: URLRequest(url: url)) {
    (data, response, error) in
    guard error == nil else {
        print("Data Task Error: \(error!)")
        return
    }

    guard let data = data else {
        print("Data Task Error: unknown")
        return
    }

    print("Data Task Success with count: \(data.count)")
}.resume()

Rxでやるなら

URLSession.shared.rx.data(request: URLRequest(url: url))
    .subscribe(onNext: { data in
        print("Data Task Success with count: \(data.count)")
    }, onError: { error in
        print("Data Task Error: \(error)")
    })
    .disposed(by: disposeBag)

コールバックは非常にシンプルになります。

Notification

従来のやり方

var ntfObserver: NSObjectProtocol!

override func viewDidLoad() {
    super.viewDidLoad()

    ntfObserver = NotificationCenter.default.addObserver(
          forName: .UIApplicationWillEnterForeground,
          object: nil, queue: nil) { (notification) in
        print("Application Will Enter Foreground")
    }
}

deinit {
    NotificationCenter.default.removeObserver(ntfObserver)
}

Rxでやるなら

override func viewDidLoad() {
    super.viewDidLoad()

    NotificationCenter.default.rx
        .notification(.UIApplicationWillEnterForeground)
        .subscribe(onNext: { (notification) in
            print("Application Will Enter Foreground")
        })
        .disposed(by: disposeBag)
}

Observerのライフサイクルを管理する必要がないため、ロジックに集中することができます。

KVO

従来のやり方

private var observerContext = 0

override func viewDidLoad() {
    super.viewDidLoad()
    user.addObserver(self, forKeyPath: #keyPath(User.name), options: [.new, .initial], context: &observerContext)
}

override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
    if context == &observerContext {
        let newValue = change?[.newKey] as? String
        print("do something with newValue")
    } else {
        super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
    }
}

deinit {
    user.removeObserver(self, forKeyPath: #keyPath(User.name))
}

Rxでやるなら

override func viewDidLoad() {
    super.viewDidLoad()

    user.rx.observe(String.self, #keyPath(User.name))
        .subscribe(onNext: { newValue in
            print("do something with newValue")
        })
        .disposed(by: disposeBag)
}

これにより、KVOのコードはより明確かつキレイになります。

複数の処理間に依存関係がある場合

例えば、最初にパスワードとユーザー名からトークンを取得し、トークンを介してユーザ情報を取得するケースです。

従来のやり方

/// コールバック・パッケージ・インタフェース
enum Api {

    /// ユーザ名とパスワードでトークンを取得する
    static func token(username: String, password: String,
        success: (String) -> Void,
        failure: (Error) -> Void) { ... }

    /// トークンによるユーザー情報の取得
    static func userinfo(token: String,
        success: (UserInfo) -> Void,
        failure: (Error) -> Void) { ... }
}

/// ユーザー名とパスワードでユーザー情報を取得する
Api.token(username: "beeth0ven", password: "987654321",
    success: { token in
        Api.userInfo(token: token,
            success: { userInfo in
                print("ユーザ情報取得成功: \(userInfo)")
            },
            failure: { error in
                print("ユーザ情報取得失敗: \(error)")
        })
    },
    failure: { error in
        print("ユーザ情報取得失敗: \(error)")
})

Rxでやるなら

/// Rxを使用したインターフェース
enum Api {

    /// ユーザ名とパスワードでトークンを取得する
    static func token(username: String, password: String) -> Observable<String> { ... }

    /// トークンによるユーザー情報の取得
    static func userInfo(token: String) -> Observable<UserInfo> { ... }
}

/// ユーザー名とパスワードでユーザー情報を取得する
Api.token(username: "beeth0ven", password: "987654321")
    .flatMapLatest(Api.userInfo)
    .subscribe(onNext: { userInfo in
        print("ユーザ情報取得成功: \(userInfo)")
    }, onError: { error in
        print("ユーザ情報取得失敗: \(error)")
    })
    .disposed(by: disposeBag)

コードのネストを少なくすることができ、コードの可読性があがります。

複数の並列処理の完了を待つ場合

例えば、2つの通信結果をマージするケースです。

Rxでやるなら

/// Rx用インターフェース
enum Api {

    /// 先生の情報を取得する
    static func teacher(teacherId: Int) -> Observable<Teacher> { ... }

    /// 先生のコメントを取得する
    static func teacherComments(teacherId: Int) -> Observable<[Comment]> { ... }
}

/// 先生の情報とコメントを同時に取得する
Observable.zip(
      Api.teacher(teacherId: teacherId),
      Api.teacherComments(teacherId: teacherId)
    ).subscribe(onNext: { (teacher, comments) in
        print("先生の情報取得成功: \(teacher)")
        print("先生のコメント取得成功: \(comments.count) 件")
    }, onError: { error in
        print("先生の情報とコメント取得失敗: \(error)")
    })
    .disposed(by: disposeBag)

なぜRxSwiftを使うのか?

  • 組み合わせ - Rxは組み合わせと同義である
  • 再利用 - 組み合わせることは容易であるため
  • クリア - 宣言は不変であるため
  • 使いやすさ - 抽象的な非同期プログラミングのために、コードスタイルを統一できる
  • 安定性 - Rxは全てユニットテストされているため

(翻訳が間違ってたら教えてくださいm(_ _)m)