中国語で書かれた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)