学习RXSwift比较好的文章:
RxSwift 系列(一) -- Observables
RxSwift 系列(二) -- Subject
RxSwift 系列(三) -- Combination Operators
RxSwift 系列(四) -- Transforming Operators
RxSwift 系列(五) -- Filtering and Conditional Operators
RxSwift 系列(六) -- Mathematical and Aggregate Operators
RxSwift 系列(七) -- Connectable Operators
RxSwift 系列(八) -- Error Handing Operators
RxSwift 系列(九) -- 那些难以理解的概念
RxSwift 实战操作【注册登录】
【iOS开发】MVVM模式快速入门
主要介绍了 Rx 的基础: Observable
。 Observable<Element>
是观察者模式中被观察的对象,相当于一个事件序列 (GeneratorType
) ,会向订阅者发送新产生的事件信息。事件信息分为三种:
- .Next(value) 表示新的事件数据。
- .Completed 表示事件序列的完结。
- .Error 同样表示完结,但是代表异常导致的完结。
为什么要使用 RxSwift ?
我们先看一下 RxSwift 能够帮助我们做些什么:
需要导入头文件
import RxSwift
import RxCocoa
Target Action
传统实现方法:
button.addTarget(self, action: #selector(buttonTapped), for: .touchUpInside)
func buttonTapped() {
print("button Tapped")
}
通过 Rx 来实现:
let disposeBag = DisposeBag()
button.rx.tap
.subscribe(onNext: {
print("button Tapped")
})
.disposed(by: disposeBag)
你不需要使用 Target Action,这样使得代码逻辑清晰可见。
代理
传统实现方法:
class ViewController: UIViewController {
...
override func viewDidLoad() {
super.viewDidLoad()
scrollView.delegate = self
}
}
extension ViewController: UIScrollViewDelegate {
func scrollViewDidScroll(_ scrollView: UIScrollView) {
print("contentOffset: \(scrollView.contentOffset)")
}
}
通过 Rx 来实现:
let disposeBag = DisposeBag()
class ViewController: UIViewController {
...
override func viewDidLoad() {
super.viewDidLoad()
scrollView.rx.contentOffset
.subscribe(onNext: { contentOffset in
print("contentOffset: \(contentOffset)")
})
.disposed(by: disposeBag)
}
}
你不需要书写代理的配置代码,就能获得想要的结果。
闭包回调
传统实现方法:
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 来实现:
let disposeBag = DisposeBag()
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)
回调也变得十分简单
通知
传统实现方法:
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 来实现:
let disposeBag = DisposeBag()
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.rx
.notification(.UIApplicationWillEnterForeground)
.subscribe(onNext: { (notification) in
print("Application Will Enter Foreground")
})
.disposed(by: disposeBag)
}
你不需要去管理观察者的生命周期,这样你就有更多精力去关注业务逻辑。
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()
let disposeBag = DisposeBag()
user.rx.observe(String.self, #keyPath(User.name))
.subscribe(onNext: { newValue in
print("do something with newValue")
})
.disposed(by: disposeBag)
}
这样实现 KVO 的代码更清晰,更简洁并且更准确。
多个任务之间有依赖关系
例如,先通过用户名密码取得 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)")
})
通过 Rx 来实现:
/// 用 Rx 封装接口
enum Api {
/// 通过用户名密码取得一个 token
static func token(username: String, password: String) -> Observable<String> { ... }
/// 通过 token 取得用户信息
static func userInfo(token: String) -> Observable<UserInfo> { ... }
}
let disposeBag = DisposeBag()
/// 通过用户名和密码获取用户信息
Api.token(username: "beeth0ven", password: "987654321")
.flatMapLatest(Api.userInfo)
.subscribe(onNext: { userInfo in
print("获取用户信息成功: \(userInfo)")
}, onError: { error in
print("获取用户信息失败: \(error)")
})
.disposed(by: disposeBag)
这样你无需嵌套太多层,从而使得代码易读,易维护。
等待多个并发任务完成后处理结果
例如,需要将两个网络请求合并成一个
通过 Rx 来实现:
/// 用 Rx 封装接口
enum Api {
/// 取得老师的详细信息
static func teacher(teacherId: Int) -> Observable<Teacher> { ... }
/// 取得老师的评论
static func teacherComments(teacherId: Int) -> Observable<[Comment]> { ... }
}
/// 同时取得老师信息和老师评论
let disposeBag = DisposeBag()
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 是完全通过单元测试的
具体语法讲解
Zip 用法
zip
合并两条队列,不过它会等到两个队列的元素一一对应地凑齐之后再合并
let stringObserver = PublishSubject<String>()
let intObserver = PublishSubject<Int>()
_ = Observable.zip(stringObserver, intObserver){
"\($0) \($1)"
}.subscribe{
print($0)
}
stringObserver.onNext("iOS")
intObserver.onNext(6)
stringObserver.onNext("swift")
intObserver.onNext(66)
stringObserver.onNext("Rx")
intObserver.onNext(666)
stringObserver.onNext("不会打印")
运行结果如下:
# map, flatMap和flatMapLatest的区别
let stream = Observable.interval(1000).take(10);
return stream.map(n => n * 2);
上面的代码模拟了异步行为,每隔1s发射一个数字。这个例子很简单,你会随着时间推移得到一连串的数字。
我们再来看另一个例子。
let stream = Observable.interval(1000).take(10);
return stream.map(n => Observable.timer(500).map(() => n));
这里stream
会返回一个Observable
而不是数字。
如果我想要拿到那些数字,我该怎么办?
let stream = Observable.interval(1000).take(10);
return stream.flatMap(n => Observable.timer(500).map(() => n));
这里使用了flatMap
而不是map
。flatMap
将响应数据“打平”,也就是说把映射后新的Observable
转化为了数据流,订阅之后会获得这个新Observable
发射的数据,而不是Observable
本身。
译者注:flatMap
有一个很适用的场景,就是搜索框。在用户输入一串字符后,将其发送到服务器并获取搜索结果,这里就涉及到两个Observable
。
Observable
.fromEvent($input, 'keyup')
.flatMap(text => getHttpResponse(text))
.subscribe(data => console.log(data))
使用flatMap
就可以直接获取到新的Observable
返回的数据。但是这里存在一个问题,如果用户有多次输入,由于网络原因可能会发生前一次响应时间比后一次长的情况,这时后一次的结果就被覆盖了。
flatMapLatest
可以解决这个问题。如果之前的Observable
还没有未触发,而又收到了新的Observable
,flatMapLatest
会取消之前的Observable
,只处理最新收到的Observable
,这样就保证了处理请求的先后顺序,flatMapLatest
在RxJS 5.x
中已更名为switchMap
。
附件:
创建Podfile 命令:
touch Podfile
Podfile 格式:
platform :ios, '9.0'
target '项目名称' do
# 用到的第三方库
pod 'AFNetworking', '~> 3.1.0'
pod 'SDWebImage', '~> 3.7.6’
pod 'MJRefresh', '~> 3.1.12'
use_frameworks!
end
然后使用终端命pod update --verbose --no-repo-update
令更新即可, 直接用pod update
会比较慢.