RxSwift 入坑好多天 - 终于有了一点理解

一、前言

江湖上都在说现在就要赶紧学 swift 了,即将是 swift 的天下了。在 api 变化不大的情况下,swift 作为一门新的语言,集众家之所长,普通编码确实比 oc 要好用的多了老早就听说 MVVM 的概念及响应式函数式编程,微软确实厉害。自己最近没什么事,就前来入坑了

二、学习方式

参考别人写的一些博客,对于概念先有个理解,然后参考官方 example,就可以开始学习了推荐文章:

三、自己写注册登录及 tableView 的一点理解

关于观察者 和 被观察者(Observable)发出序列

UI 控件在 RxCocoa 下某些属性都是被观察者(Observable),都可以发出序列,常见的有

控件的 text 类型是 ControlProperty ,最终遵循 ObservableType协议按钮的点击 tap 类型是 ControlEvent,最终遵循 ObservableType协议 对于设置 UI 控件的一些 Bool 类型的属性,如可输入,可点击,一般用 UIBindingObserver(遵循 ObserverType协议) 来生成观察者,对接受的数据条件进行判断是否可以输入、可点击

// MARK: RX 扩展 计算型属性

// textfield 根据展示验证后的结果能否输入,验证过了才能输入

extension Reactive where Base: UITextField {

var inputEnable: UIBindingObserver {

return UIBindingObserver(UIElement: base, binding: {

(textField, result) in

textField.isEnabled = result.isValid

})

}

} 关于在 VM 中常用的 Subject

Variable、PublishSubject 是 Subject 的一种,可当观察者被 bindTo,可当序列数据源 Observable

Variable 它不会因为错误终止也不会正常终止, 适合做数据源,可以用于控件的 text 属性PublishSubject 与普通的Subject不同,在订阅时并不立即触发订阅事件,而是允许我们在任意时刻手动调用onNext(),onError(),onCompleted来触发事件,可以用于按钮的点击 关于被观察者(Observable)的一些常用的 api

map 不会产生新的序列flatMapLatest 会产生新的序列combineLatest 不会产生新的序列 关于 MVVM 文件夹分类

之前 MVC 的与 iOS 里的 Controller、View 一一对应,很好理解,而 MVVM 里 Controller 属于 V 了,负责处理控制器跳转和将 View 和 VM 绑定等,大部分的业务逻辑代码都在 VM 里,感觉应该是这样

自始至终感觉 iOS 里的 model 这一层很轻,有时仅仅是建立了模型类而已。感觉应该是各种数据操作如数据库查询等都应该是 model 这一层的

关于** 双向绑定 **

首先要有一些控件,理清楚需要监听这些控件的哪些属性值然后 VM 里建立好这些属性值对应的 Subject一般控制器里生成 VM 对象,将控件的 Observable 的属性绑定到 VM 的 Subject 属性上,这样可在 VM 里监听到控件属性值的改变,此时 Subject 是 Observer,完成一次绑定在 VM 内,将 Subject 变成 Observable,生成对应 VM 可被观察者属性(用属性保存加工变换后的 Observable )。这里 Subject 是 Observable,可通过 map 、filter 等各种操作,操作的数据就是 Subject 观察到的序列,相应模块的整个业务逻辑都在此处。在控制器里,再将 VM 的可被观察者属性绑定到 UI 控件上,在此完成双向绑定 override func viewDidLoad() {

super.viewDidLoad()

let regiestViewModel = RegiestViewModel()

// 这里做绑定: UI控件 --> VM VM -> UI控件

// 1.UI控件 --> VM

nameTextField.rx.text.orEmpty

.bindTo(regiestViewModel.username)

.addDisposableTo(disposeBag)

pwdTextField.rx.text.orEmpty

.bindTo(regiestViewModel.userPwd)

.addDisposableTo(disposeBag)

repeatPwdTextField.rx.text.orEmpty

.bindTo(regiestViewModel.repeatPwd)

.addDisposableTo(disposeBag)

regiestBtn.rx.tap

.bindTo(regiestViewModel.registerTaps)

.addDisposableTo(disposeBag)

// 2.VM -> UI控件

// 显示结果的 label 上

regiestViewModel.usernameValid

.bindTo(nameTipLabel.rx.validResult)

.addDisposableTo(disposeBag)

// 绑定 密码框是否可以输入

regiestViewModel.usernameValid

.bindTo(pwdTextField.rx.inputEnable)

.addDisposableTo(disposeBag)

regiestViewModel.passwordValid

.bindTo(pwdTipLabel.rx.validResult)

.addDisposableTo(disposeBag)

regiestViewModel.passwordValid

.bindTo(repeatPwdTextField.rx.inputEnable)

.addDisposableTo(disposeBag)

regiestViewModel.repeatPwdValid

.bindTo(repeatPwdTipLabel.rx.validResult)

.addDisposableTo(disposeBag)

// 按钮不是绑定, 按钮是 subcribe, 需要操作的

regiestViewModel.registerButtonEnabled

.subscribe (onNext: { [weak self] (result) in

self?.regiestBtn.isEnabled = result

self?.regiestBtn.alpha = result ? 1 : 0.8

})

.addDisposableTo(disposeBag)

// 注册结果 : 注册成果或失败 要展示在 UI 上

regiestViewModel.registeResult

.subscribe(onNext:{ [weak self] result in

switch result {

case let .failed(message):

self?.showAlter(message: message)

case let .ok(message):

self?.showAlter(message: message)

case .empty:

self?.showAlter(message: "")

}

})

.addDisposableTo(disposeBag)

// 跳转到登录界面按钮的点击

loginVcBtn.rx.tap

.subscribe(onNext: {

let loginVc = LoginViewController()

loginVc.title = "请登录"

self.navigationController?.pushViewController(loginVc, animated: true)

})

.addDisposableTo(disposeBag)

}

关于 tableView

这里需要 RxDataSources 这个配套的框架 控制器里需要一个 dataSource

let dataSource = RxTableViewSectionedReloadDataSource>() 控制器里用这个 dataSource 配置 cell

dataSource.configureCell = { (_, tableView, indexPath, item) in

var cell = tableView.dequeueReusableCell(withIdentifier: "herosCell")

if cell == nil {

cell = UITableViewCell(style: .subtitle, reuseIdentifier: "herosCell")

}

cell!.imageView?.image = UIImage(named: item.icon)

cell!.textLabel?.text = item.name

cell!.detailTextLabel?.text = item.intro

return cell!

}

用 VM 创建出数据 Observable,发出序列,绑定到dataSource 上,完成数据的绑定

homeViewMode.getSearchResult()

.bindTo(tableView.rx.items(dataSource: dataSource))

.addDisposableTo(disposeBag) 其他操作

tableView 的代理

tableView.rx

.setDelegate(self)

.addDisposableTo(disposeBag) tableView cell 的点击

tableView.rx.itemSelected

.map { [weak self] indexPath in

return (indexPath, self?.dataSource[indexPath])

}

.subscribe(onNext: {(indexPath, item) in

self.showAlter(item: item)

})

.addDisposableTo(disposeBag) 感觉像一个固定的代码模式,将数据源的代码都移到 VM 里了 另外如果做实时搜索的话,用双向绑定效果那是极好的,将搜索框的搜索关键字绑定到 VM 里,在用 VM 产生序列绑定到 tableView 上

一、前言

江湖上都在说现在就要赶紧学 swift 了,即将是 swift 的天下了。在 api 变化不大的情况下,swift 作为一门新的语言,集众家之所长,普通编码确实比 oc 要好用的多了老早就听说 MVVM 的概念及响应式函数式编程,微软确实厉害。自己最近没什么事,就前来入坑了

二、学习方式

参考别人写的一些博客,对于概念先有个理解,然后参考官方 example,就可以开始学习了推荐文章: http://www.codertian.com/2016/11/27/RxSwift-ru-keng-ji-read-document/ http://www.codertian.com/2016/12/10/RxSwift-shi-zhan-jie-du-base-demo/

三、自己写注册登录及 tableView 的一点理解

关于观察者 和 被观察者(Observable)发出序列

UI 控件在 RxCocoa 下某些属性都是被观察者(Observable),都可以发出序列,常见的有

控件的 text 类型是 ControlProperty ,最终遵循 ObservableType协议按钮的点击 tap 类型是 ControlEvent,最终遵循 ObservableType协议 对于设置 UI 控件的一些 Bool 类型的属性,如可输入,可点击,一般用 UIBindingObserver(遵循 ObserverType协议) 来生成观察者,对接受的数据条件进行判断是否可以输入、可点击

// MARK: RX 扩展 计算型属性

// textfield 根据展示验证后的结果能否输入,验证过了才能输入

extension Reactive where Base: UITextField {

var inputEnable: UIBindingObserver {

return UIBindingObserver(UIElement: base, binding: {

(textField, result) in

textField.isEnabled = result.isValid

})

}

} 关于在 VM 中常用的 Subject

Variable、PublishSubject 是 Subject 的一种,可当观察者被 bindTo,可当序列数据源 Observable

Variable 它不会因为错误终止也不会正常终止, 适合做数据源,可以用于控件的 text 属性PublishSubject 与普通的Subject不同,在订阅时并不立即触发订阅事件,而是允许我们在任意时刻手动调用onNext(),onError(),onCompleted来触发事件,可以用于按钮的点击 关于被观察者(Observable)的一些常用的 api

map 不会产生新的序列flatMapLatest 会产生新的序列combineLatest 不会产生新的序列 关于 MVVM 文件夹分类

之前 MVC 的与 iOS 里的 Controller、View 一一对应,很好理解,而 MVVM 里 Controller 属于 V 了,负责处理控制器跳转和将 View 和 VM 绑定等,大部分的业务逻辑代码都在 VM 里,感觉应该是这样

自始至终感觉 iOS 里的 model 这一层很轻,有时仅仅是建立了模型类而已。感觉应该是各种数据操作如数据库查询等都应该是 model 这一层的

关于** 双向绑定 **

首先要有一些控件,理清楚需要监听这些控件的哪些属性值然后 VM 里建立好这些属性值对应的 Subject一般控制器里生成 VM 对象,将控件的 Observable 的属性绑定到 VM 的 Subject 属性上,这样可在 VM 里监听到控件属性值的改变,此时 Subject 是 Observer,完成一次绑定在 VM 内,将 Subject 变成 Observable,生成对应 VM 可被观察者属性(用属性保存加工变换后的 Observable )。这里 Subject 是 Observable,可通过 map 、filter 等各种操作,操作的数据就是 Subject 观察到的序列,相应模块的整个业务逻辑都在此处。在控制器里,再将 VM 的可被观察者属性绑定到 UI 控件上,在此完成双向绑定 override func viewDidLoad() {

super.viewDidLoad()

let regiestViewModel = RegiestViewModel()

// 这里做绑定: UI控件 --> VM VM -> UI控件

// 1.UI控件 --> VM

nameTextField.rx.text.orEmpty

.bindTo(regiestViewModel.username)

.addDisposableTo(disposeBag)

pwdTextField.rx.text.orEmpty

.bindTo(regiestViewModel.userPwd)

.addDisposableTo(disposeBag)

repeatPwdTextField.rx.text.orEmpty

.bindTo(regiestViewModel.repeatPwd)

.addDisposableTo(disposeBag)

regiestBtn.rx.tap

.bindTo(regiestViewModel.registerTaps)

.addDisposableTo(disposeBag)

// 2.VM -> UI控件

// 显示结果的 label 上

regiestViewModel.usernameValid

.bindTo(nameTipLabel.rx.validResult)

.addDisposableTo(disposeBag)

// 绑定 密码框是否可以输入

regiestViewModel.usernameValid

.bindTo(pwdTextField.rx.inputEnable)

.addDisposableTo(disposeBag)

regiestViewModel.passwordValid

.bindTo(pwdTipLabel.rx.validResult)

.addDisposableTo(disposeBag)

regiestViewModel.passwordValid

.bindTo(repeatPwdTextField.rx.inputEnable)

.addDisposableTo(disposeBag)

regiestViewModel.repeatPwdValid

.bindTo(repeatPwdTipLabel.rx.validResult)

.addDisposableTo(disposeBag)

// 按钮不是绑定, 按钮是 subcribe, 需要操作的

regiestViewModel.registerButtonEnabled

.subscribe (onNext: { [weak self] (result) in

self?.regiestBtn.isEnabled = result

self?.regiestBtn.alpha = result ? 1 : 0.8

})

.addDisposableTo(disposeBag)

// 注册结果 : 注册成果或失败 要展示在 UI 上

regiestViewModel.registeResult

.subscribe(onNext:{ [weak self] result in

switch result {

case let .failed(message):

self?.showAlter(message: message)

case let .ok(message):

self?.showAlter(message: message)

case .empty:

self?.showAlter(message: "")

}

})

.addDisposableTo(disposeBag)

// 跳转到登录界面按钮的点击

loginVcBtn.rx.tap

.subscribe(onNext: {

let loginVc = LoginViewController()

loginVc.title = "请登录"

self.navigationController?.pushViewController(loginVc, animated: true)

})

.addDisposableTo(disposeBag)

}

关于 tableView

这里需要 RxDataSources 这个配套的框架 控制器里需要一个 dataSource

let dataSource = RxTableViewSectionedReloadDataSource>() 控制器里用这个 dataSource 配置 cell

dataSource.configureCell = { (_, tableView, indexPath, item) in

var cell = tableView.dequeueReusableCell(withIdentifier: "herosCell")

if cell == nil {

cell = UITableViewCell(style: .subtitle, reuseIdentifier: "herosCell")

}

cell!.imageView?.image = UIImage(named: item.icon)

cell!.textLabel?.text = item.name

cell!.detailTextLabel?.text = item.intro

return cell!

}

用 VM 创建出数据 Observable,发出序列,绑定到dataSource 上,完成数据的绑定

homeViewMode.getSearchResult()

.bindTo(tableView.rx.items(dataSource: dataSource))

.addDisposableTo(disposeBag) 其他操作

tableView 的代理

tableView.rx

.setDelegate(self)

.addDisposableTo(disposeBag) tableView cell 的点击

tableView.rx.itemSelected

.map { [weak self] indexPath in

return (indexPath, self?.dataSource[indexPath])

}

.subscribe(onNext: {(indexPath, item) in

self.showAlter(item: item)

})

.addDisposableTo(disposeBag) 感觉像一个固定的代码模式,将数据源的代码都移到 VM 里了 另外如果做实时搜索的话,用双向绑定效果那是极好的,将搜索框的搜索关键字绑定到 VM 里,在用 VM 产生序列绑定到 tableView 上