一、 使用 RxSwift 与传统方式对比
1. 按钮添加点击事件
传统方式
button.addTarget(self, action: #selector(buttonTapped), for: .touchUpInside)
使用RxSwift
不再需要使用 Target Action
button.rx.tap.subscribe(onNext: {
print("button Tapped")
}) .disposed(by: disposeBag)
事件监听和处理在一个地方,更易维护
2. 代理
传统方式
class ViewController: UIViewController {
...
override func viewDidLoad() {
super.viewDidLoad()
scrollView.delegate = self
}
}
extension ViewController: UIScrollViewDelegate {
func scrollViewDidScroll(_ scrollView: UIScrollView) {
print("contentOffset: \(scrollView.contentOffset)")
}
}
使用RxSwift后
scrollView.rx.contentOffset
.subscribe(onNext: { contentOffset in
print("contentOffset: \(contentOffset)")
})
.disposed(by: disposeBag)
3. 通知
传统方式
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")
}
}
// Remove
deinit {
NotificationCenter.default.removeObserver(ntfObserver)
}
使用RxSwift后
NotificationCenter.default.rx
.notification(.UIApplicationWillEnterForeground)
.subscribe(onNext: { (notification) in
print("Application Will Enter Foreground")
}) .disposed(by: disposeBag)
不再需要管理通知的生命周期
4. 多个任务之间有依赖关系
需求:先通过用户名密码取得 Token 然后通过 Token 取得用户信息,
/// 用回调的方式封装接口
enum API {
/// 通过用户名密码取得一个 token
static func token(username: String, password: String,
success: (String) -> Void,
failure: (Error) -> Void) { ... }
/// 通过 token 取得用户信息
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)")
})
如果使用RxSwift
/// 用 Rx 封装接口
enum API {
/// 通过用户名密码取得一个 token
static func token(username: String, password: String) -> Observable<String> { ... }
/// 通过 token 取得用户信息
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)
避免回调地狱
5. 等待多个并发任务完成后处理结果
需求: 需要将两个网络请求合并成一个
/// 用 Rx 封装接口
enum API {
/// fetch feed list
static func postList() -> Observable<Post> { ... }
/// fetch unread count
static func unreadCount() -> Observable<Int> { ... }
}
/// 同时取得帖子列表和未读消息数量
Observable.zip(
API.postList(),
API.unreadCount()
).subscribe(onNext: { (posts, unreadCount) in
print("获取Feed成功: \(posts.count)")
print("获取未读成功: \(unreadCount) ")
}, onError: { error in
print("获取失败: \(error)")
})
.disposed(by: disposeBag)
二、 基本思想
Sequences / Observables
Sequence协议
Swift中Array遵循了Sequence协议。
处理Array中的元素,并读取
let array = [1, 2, 3, 4, 5]
// Filter Map
let array2 = array.filter({ $0 > 1 }).map({ $0 * 2 })//4 6 8 10
// 创建迭代器
var indexGenerator = array2.makeIterator()
// Call next()
let fisrt = indexGenerator.next() // 4
let seoncd = indexGenerator.next() //6
RxSwift 基本思想与此类似。 RxSwift 生成的是一个可监听的序列,可异步监听。与Array不同,它是时间概念上的序列,不会同时触发。无状态。
三、RxSwift 核心概念
- Observable - 产生事件,可监听可观察
- Observer - 响应事件,观察者
- Operator - 创建变化组合事件,操作符
- Disposable - 管理绑定(订阅)的生命周期
- Schedulers - 线程队列调配
1. Observable<T> 产生事件,可监听可观察
Observable<T> 它的作用就是可以异步地产生一系列的 Event(事件),即一个 Observable<T> 对象会随着时间推移不定期地发出 event(element : T) 这样一个东西。
而且这些 Event 还可以携带数据,它的泛型 <T> 就是用来指定这个Event携带的数据的类型。
public enum Event<Element> {
/// Next element is produced.
case next(Element)
/// Sequence terminated with an error.
case error(Swift.Error)
/// Sequence completed successfully.
case completed
}
更好理解:我们可以把每一个 Observable 的实例想象成于一个 Swift 中的 Sequence.
2. Observer - 响应事件,观察者
观察者 是用来监听事件,它需要这个事件做出响应。
例如:弹出提示框就是观察者,它对点击按钮这个事件做出响应。
tap.subscribe(onNext: { [weak self] in
self?.showAlert()
}, onError: { error in
print("Error Happened: \(error.localizedDescription)")
}, onCompleted: {
print("done")
})
创建观察者
2.1 直接接上Subscribe
2.2 Binder 特征观察者 usernameTxtField.rx.text
- 不会处理错误事件
- 确保绑定都是在给定 Scheduler 上执行(默认 MainScheduler)
extension Reactive where Base: UITextField {
/// Reactive wrapper for `text` property.
public var text: ControlProperty<String?> {
value
}
/// Reactive wrapper for `text` property.
public var value: ControlProperty<String?> {
return base.rx.controlPropertyWithDefaultEvents(
getter: { textField in
textField.text
},
setter: { textField, value in
// This check is important because setting text value always clears control state
// including marked text selection which is imporant for proper input
// when IME input method is used.
if textField.text != value {
textField.text = value
}
}
)
}
3. Operator - 创建变化组合事件,操作符
操作符可以帮助创建新的序列,或者变化组合原有的序列,从而生成一个新的序列。
.debunce 过滤掉高频产生的元素
.filter 按条件过滤
textField.rx.text
.filter({$0.count > 3})
.debounce(0.5, .mainScheduler)
.subscribe{ }
操作符决策树:
http://reactivex.io/documentation/operators.html#tree
4. Disposable - 管理绑定(订阅)的生命周期
一个序列如果发出了 error 或者 completed 事件,那么所有内部资源都会被释放。
两种方式:
- 如果需要提前释放这些资源或取消订阅的话,可以对观察者返回的 可被清除的资源(Disposable) 调用 dispose
var disposeBag = DisposeBag()
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
textField.rx.text.orEmpty
.subscribe(onNext: { text in print(text) })
.disposed(by: self.disposeBag)
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
self.disposeBag = DisposeBag()
}
- 使用 takeUntil 操作符,绑定生命周期到其他对象上
override func viewDidLoad() {
super.viewDidLoad()
_ = usernameValid
.take(until: rx.deallocated)
.bind(to: passwordOutlet.rx.isEnabled)
5. Schedulers - 线程队列调配
Schedulers 是 Rx 实现多线程的核心模块,它主要用于控制任务在哪个线程或队列运行。
// 后台取得数据,主线程处理结果
DispatchQueue.global(qos: .userInitiated).async {
let data = try? Data(contentsOf: url)
DispatchQueue.main.async {
self.data = data
}
}
在RxSwift 中
rxData
.subscribeOn(ConcurrentDispatchQueueScheduler(qos: .userInitiated))
.observeOn(MainScheduler.instance)
.subscribe(onNext: { [weak self] data in
self?.data = data
})
.disposed(by: disposeBag)
说明:
.subscribe(on: MainScheduler.instance)
决定数据序列的构建函数在哪个 Scheduler 上运行。
.observe(on: MainScheduler.instance)
决定在哪个 Scheduler 监听这个数据序列
- MainScheduler
代表主线程,处理UI任务 - SerialDispatchQueueScheduler
抽象了串行 DispatchQueue - ConcurrentDispatchQueueScheduler
抽象了并行 DispatchQueue - OperationQueueScheduler
抽象了 NSOperationQueue。可设置 maxConcurrentOperationCount 来控制最大并发数
四、基础使用
- 当用户输入用户名时,如果用户名不足 5 个字就给出提示语,并且无法输入密码,当用户名符合要求时才可以输入密码。
- 当用户输入的密码不到 5 个字时也给出红色提示语。
- 当用户名和密码有一个不符合要求时底部的绿色按钮不可点击,只有当用户名和密码同时有效时按钮才可点击。
- 当点击按钮后弹出一个提示框。
Code
五、突出优势
- 易复合 - 一堆Observable,随意组合
- 高复用 - 因为它易复合
- 清晰 - 代码量降低, 更专注在业务上
六、参考
Dev tips
- 在 protocol 里面加 变量
-
字面量表达式:
public func print<T>(file: String = #file, function: String = #function, line: Int = #line, _ message: T, color: UIColor = .white) {
}