RxSwift ViewModel定义
观察者模式
观察者模式目标:定义对象间一对多的依赖关系,当一个对象(被观察者)状态发生改变时,所有依赖于它的对象(观察者)都得到通知并被自动更新。
对于RxSwift就是序列事件(元素)的发出与订阅。
ViewModel定义
viewModel定义主要有两种方式,一种是初始化传入所有依赖,另一种是遵守ViewModelable协议,并且实现Input和Output的定义,在transform中进行绑定。
基于transform的ViewModel
遵守ViewModelable协议,或者自定义,绑定过程和逻辑处理在transform方法中,
优点: 输入输出明显,变化灵活,初始化时不需要传入其他参数。
ViewModelable
该协议用于规范Rx 情况下viewModel
public protocol ViewModelable {
associatedtype Input // 输入, 遵守时需要定义
associatedtype Output // 输出, 遵守时需要定义
func transform(input: Input) -> Output // 转换, 通过输入转化(绑定与逻辑)到输出
}
Intput: 逻辑依赖需要的东西
Output: 逻辑后的东西
NormalViewModel
由于大部分ViewModel都需要管理包, 和需要提供loading状态的被观察者,所以定义一个常用基本ViewModel,可以用于ViewModel继承。
public class NormalViewModel: NSObject {
var disposeBag = DisposeBag() // 管理包
let loading: BehaviorRelay<Bool> = BehaviorRelay(value: false) //loading状态(比如网络请求中,图片上传中)
}
关于disposeBag: 管理包的生命周期决定订阅的生命周期,注意ViewModel生命周期需要与视图一致,否则可能提早释放导致viewModel内部bag无法持有订阅。(即不可将vimeModel声明在方法内), 如果无法保证,则从外部传入disposeBag
TableViewModel
对于表格来说,可能具有是否有下一页、空白占位背景等通用属性。所以定义TableViewModel用于tableView的viewModel继承。
public class TableViewModel: NormalViewModel {
/// 是否有下一页,分页使用
let hasLastPage: BehaviorRelay<Bool> = BehaviorRelay(value: false)
/// tableView空白占位,一共有三中状态, 无占位、无网络、无数据
let noDataBackground: BehaviorRelay<UIScrollView.NoDataType> = BehaviorRelay(value: UIScrollView.NoDataType.hidden)
/// 通知空白背景添加, 此时reload点击才有效
///
/// **示例**
/// ```
/// switch result {
/// case .failure(_):
/// self.noDataBackground.accept(.noNetwork)
/// self.noDataBackgroundAdd.accept(())
/// case .success(let models):
/// if models.isEmpty {
/// self.noDataBackground.accept(.noData)
/// self.noDataBackgroundAdd.accept(())
/// } else {
/// self.noDataBackground.accept(.hidden)
/// self.datasource.onNext(models)
/// }
/// }
/// ```
let noDataBackgroundAdd: BehaviorRelay<Void> = BehaviorRelay(value: ())
}
基于transform的ViewModel定义
参考RxSwiftExample ->LoginViewModel.swift 的LoginViewModel
// MARK: - 基于transform的ViewModel
class LoginViewModel: NormalViewModel, ViewModelable {
struct Input {
let username: ControlProperty<String>
let password: ControlProperty<String>
let login: ControlEvent<Void>
}
struct Output {
let allowLogin: Driver<Bool>
}
func transform(input: Input) -> Output {
input.login
.subscribe(onNext: { (_) in
print("网络请求")
})
.disposed(by: disposeBag)
let allowLogin = Observable.combineLatest(input.username, input.password).flatMap { (username, password) -> Observable<Bool> in
/// 假设用户名和密码都大于2 即可登录
if username.count > 2 && password.count > 2 {
return Observable.just(true)
} else {
return Observable.just(false)
}
}
return Output(allowLogin: allowLogin.asDriver(onErrorJustReturn: true))
}
}
基于初始化ViewModel
基于初始化的ViewModel,即在初始化时就传入依赖参数,bind和逻辑部分在初始化完成。
参考RxSwiftExample ->LoginViewModel.swift 的LoginViewModel1
// MARK: - 基于初始化的ViweModel
struct LoginViewModel1 {
let disposeBag = DisposeBag()
var allowLogin: Driver<Bool> = Driver.just(false)
init(input:(username: ControlProperty<String>, password: ControlProperty<String>, login: ControlEvent<Void>)) {
input.login
.subscribe(onNext: { (_) in
print("网络请求")
})
.disposed(by: disposeBag)
allowLogin = Observable.combineLatest(input.username, input.password).flatMap { (username, password) -> Observable<Bool> in
/// 假设用户名和密码都大于2 即可登录
if username.count > 2 && password.count > 2 {
return Observable.just(true)
} else {
return Observable.just(false)
}
}.asDriver(onErrorJustReturn: true)
}
}
如何选择哪种方式?
建议使用基于transform实现ViewModelable,更加灵活和规范。当viewModel较为简单时,可以使用基于初始化。
参考项目:ViewModelDemo