如何在 viewmodel 中配置 RxSwift observable
How to dispose RxSwift observable in viewmodel
我正在学习 RxSwift,我已经尝试使用它进行基本登录 UI。我的实现如下
LoginViewController:
fileprivate let loginViewModel = LoginViewModel()
fileprivate var textFieldArray: [UITextField]!
override func viewDidLoad() {
super.viewDidLoad()
textFieldArray = [textFieldUserName, textFieldPassword, textFieldConfirmPassword]
textFieldUserName.delegate = self
textFieldPassword.delegate = self
textFieldConfirmPassword.delegate = self
loginViewModel.areValidFields.subscribe(
onNext: { [weak self] validArray in
for i in 0..<validArray.count {
if validArray[i] {
self?.showValidUI(index: i)
} else {
self?.showInValidUI(index: i)
}
}
},
onCompleted: {
print("### COMPLETED ###")
},
onDisposed: {
print("### DISPOSED ###")
}).disposed(by: loginViewModel.bag)
}
func showValidUI(index: Int) {
textFieldArray[index].layer.borderColor = UIColor.clear.cgColor
}
func showInValidUI(index: Int) {
textFieldArray[index].layer.borderColor = UIColor.red.cgColor
textFieldArray[index].layer.borderWidth = 2.0
}
extension LoginViewController: UITextFieldDelegate {
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
let inputText = (textField.text! as NSString).replacingCharacters(in: range, with: string)
switch textField {
case textFieldUserName:
loginViewModel.updateUserName(text: inputText)
case textFieldPassword:
loginViewModel.updatePassword(text: inputText)
case textFieldConfirmPassword:
loginViewModel.updateConfirmedPassword(text: inputText)
default:
return false
}
return true
}
}
LoginViewModel:
class LoginViewModel {
private var username: String!
private var password: String!
private var confirmedPassword: String!
fileprivate let combinedSubject = PublishSubject<[Bool]>()
let bag = DisposeBag()
var areValidFields: Observable<[Bool]> {
return combinedSubject.asObservable()
}
init() {
self.username = ""
self.password = ""
self.confirmedPassword = ""
}
/*deinit {
combinedSubject.onCompleted()
}*/
func updateUserName(text: String) {
username = text
if username.count > 6 {
combinedSubject.onNext([true, true, true])
} else {
combinedSubject.onNext([false, true, true])
}
}
func updatePassword(text: String) {
password = text
if password.count > 6 {
combinedSubject.onNext([true, true, true])
} else {
combinedSubject.onNext([true, false, true])
}
}
func updateConfirmedPassword(text: String) {
confirmedPassword = text
if confirmedPassword == password {
combinedSubject.onNext([true, true, true])
} else {
combinedSubject.onNext([true, true, false])
}
}
}
使用此代码,当我移回导航堆栈时打印已处理的消息。
但是,如果我继续前进,则不会打印已处理的消息。处理可观察对象的正确方法是什么?
您已将 disposable 添加到 LoginViewModel
对象的处理包中,该对象在 LoginViewController
对象被释放时被释放。
这意味着直到 LoginViewController
被释放或者您在 areValidFields
Observable.
上收到完成或错误后,LoginViewModel observable 返回的可释放对象才会被释放。
这与大多数可观察到的案例中公认的行为同步。
但是,如果你想在 LoginViewController 移出屏幕时处理 observable,你可以手动处理:
var areValidFieldsDisposbale:Disposable?
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
areValidFieldsDisposbale = loginViewModel.areValidFields.subscribe(
onNext: { [weak self] validArray in
for i in 0..<validArray.count {
if validArray[i] {
self?.showValidUI(index: i)
} else {
self?.showInValidUI(index: i)
}
}
},
onCompleted: {
print("### COMPLETED ###")
},
onDisposed: {
print("### DISPOSED ###")
})
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
areValidFieldsDisposbale?.dispose()
}
当您向前移动时,视图控制器不会从堆栈中移除。它保持不变,以便当用户点击后退按钮时,它已准备就绪并且仍处于与用户上次看到它时相同的状态。这就是为什么什么都没有处理的原因。
另外,既然你说你还在学习 Rx,那么你所拥有的离最佳实践还很远。我希望看到更多类似这样的内容:
class LoginViewModel {
let areValidFields: Observable<[Bool]>
init(username: Observable<String>, password: Observable<String>, confirm: Observable<String>) {
let usernameValid = username.map { [=10=].count > 6 }
let passValid = password.map { [=10=].count > 6 }
let confirmValid = Observable.combineLatest(password, confirm)
.map { [=10=] == }
areValidFields = Observable.combineLatest([usernameValid, passValid, confirmValid])
}
}
在您的视图模型中,更喜欢在 init
函数中接受输入。如果你不能这样做,例如如果某些输入尚不存在,则使用 Subject 属性 并绑定到它。但是在任何一种情况下,您的视图模型基本上应该只包含一个初始化函数和一些输出属性。 DisposeBag
不会 进入视图模型。
您的视图控制器只需要创建一个视图模型并连接到它:
class LoginViewController: UIViewController {
@IBOutlet weak var textFieldUserName: UITextField!
@IBOutlet weak var textFieldPassword: UITextField!
@IBOutlet weak var textFieldConfirmPassword: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
let viewModel = LoginViewModel(
username: textFieldUserName.rx.text.orEmpty.asObservable(),
password: textFieldPassword.rx.text.orEmpty.asObservable(),
confirm: textFieldConfirmPassword.rx.text.orEmpty.asObservable()
)
let textFieldArray = [textFieldUserName!, textFieldPassword!, textFieldConfirmPassword!]
viewModel.areValidFields.subscribe(
onNext: { validArray in
for (field, valid) in zip(textFieldArray, validArray) {
if valid {
field.layer.borderColor = UIColor.clear.cgColor
}
else {
field.layer.borderColor = UIColor.red.cgColor
field.layer.borderWidth = 2.0
}
}
})
.disposed(by: bag)
}
private let bag = DisposeBag()
}
请注意,所有代码都在 viewDidLoad
函数中结束。这是理想情况,因此您不必处理 [weak self]
。在这种特殊情况下,我可能会将 onNext
闭包放在柯里化的全局函数中,在这种情况下它看起来像这样:
class LoginViewController: UIViewController {
@IBOutlet weak var textFieldUserName: UITextField!
@IBOutlet weak var textFieldPassword: UITextField!
@IBOutlet weak var textFieldConfirmPassword: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
let viewModel = LoginViewModel(
username: textFieldUserName.rx.text.orEmpty.asObservable(),
password: textFieldPassword.rx.text.orEmpty.asObservable(),
confirm: textFieldConfirmPassword.rx.text.orEmpty.asObservable()
)
let textFieldArray = [textFieldUserName!, textFieldPassword!, textFieldConfirmPassword!]
viewModel.areValidFields.subscribe(
onNext:update(fields: textFieldArray))
.disposed(by: bag)
}
private let bag = DisposeBag()
}
func update(fields: [UITextField]) -> ([Bool]) -> Void {
return { validArray in
for (field, valid) in zip(fields, validArray) {
if valid {
field.layer.borderColor = UIColor.clear.cgColor
}
else {
field.layer.borderColor = UIColor.red.cgColor
field.layer.borderWidth = 2.0
}
}
}
}
注意这里update(fields:)
函数在class中是而不是。这样我们就不会捕获自我,因此不必担心自我脆弱。此外,此更新功能可能对应用中的其他表单输入非常有用。
我正在学习 RxSwift,我已经尝试使用它进行基本登录 UI。我的实现如下
LoginViewController:
fileprivate let loginViewModel = LoginViewModel()
fileprivate var textFieldArray: [UITextField]!
override func viewDidLoad() {
super.viewDidLoad()
textFieldArray = [textFieldUserName, textFieldPassword, textFieldConfirmPassword]
textFieldUserName.delegate = self
textFieldPassword.delegate = self
textFieldConfirmPassword.delegate = self
loginViewModel.areValidFields.subscribe(
onNext: { [weak self] validArray in
for i in 0..<validArray.count {
if validArray[i] {
self?.showValidUI(index: i)
} else {
self?.showInValidUI(index: i)
}
}
},
onCompleted: {
print("### COMPLETED ###")
},
onDisposed: {
print("### DISPOSED ###")
}).disposed(by: loginViewModel.bag)
}
func showValidUI(index: Int) {
textFieldArray[index].layer.borderColor = UIColor.clear.cgColor
}
func showInValidUI(index: Int) {
textFieldArray[index].layer.borderColor = UIColor.red.cgColor
textFieldArray[index].layer.borderWidth = 2.0
}
extension LoginViewController: UITextFieldDelegate {
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
let inputText = (textField.text! as NSString).replacingCharacters(in: range, with: string)
switch textField {
case textFieldUserName:
loginViewModel.updateUserName(text: inputText)
case textFieldPassword:
loginViewModel.updatePassword(text: inputText)
case textFieldConfirmPassword:
loginViewModel.updateConfirmedPassword(text: inputText)
default:
return false
}
return true
}
}
LoginViewModel:
class LoginViewModel {
private var username: String!
private var password: String!
private var confirmedPassword: String!
fileprivate let combinedSubject = PublishSubject<[Bool]>()
let bag = DisposeBag()
var areValidFields: Observable<[Bool]> {
return combinedSubject.asObservable()
}
init() {
self.username = ""
self.password = ""
self.confirmedPassword = ""
}
/*deinit {
combinedSubject.onCompleted()
}*/
func updateUserName(text: String) {
username = text
if username.count > 6 {
combinedSubject.onNext([true, true, true])
} else {
combinedSubject.onNext([false, true, true])
}
}
func updatePassword(text: String) {
password = text
if password.count > 6 {
combinedSubject.onNext([true, true, true])
} else {
combinedSubject.onNext([true, false, true])
}
}
func updateConfirmedPassword(text: String) {
confirmedPassword = text
if confirmedPassword == password {
combinedSubject.onNext([true, true, true])
} else {
combinedSubject.onNext([true, true, false])
}
}
}
使用此代码,当我移回导航堆栈时打印已处理的消息。
但是,如果我继续前进,则不会打印已处理的消息。处理可观察对象的正确方法是什么?
您已将 disposable 添加到 LoginViewModel
对象的处理包中,该对象在 LoginViewController
对象被释放时被释放。
这意味着直到 LoginViewController
被释放或者您在 areValidFields
Observable.
这与大多数可观察到的案例中公认的行为同步。
但是,如果你想在 LoginViewController 移出屏幕时处理 observable,你可以手动处理:
var areValidFieldsDisposbale:Disposable?
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
areValidFieldsDisposbale = loginViewModel.areValidFields.subscribe(
onNext: { [weak self] validArray in
for i in 0..<validArray.count {
if validArray[i] {
self?.showValidUI(index: i)
} else {
self?.showInValidUI(index: i)
}
}
},
onCompleted: {
print("### COMPLETED ###")
},
onDisposed: {
print("### DISPOSED ###")
})
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
areValidFieldsDisposbale?.dispose()
}
当您向前移动时,视图控制器不会从堆栈中移除。它保持不变,以便当用户点击后退按钮时,它已准备就绪并且仍处于与用户上次看到它时相同的状态。这就是为什么什么都没有处理的原因。
另外,既然你说你还在学习 Rx,那么你所拥有的离最佳实践还很远。我希望看到更多类似这样的内容:
class LoginViewModel {
let areValidFields: Observable<[Bool]>
init(username: Observable<String>, password: Observable<String>, confirm: Observable<String>) {
let usernameValid = username.map { [=10=].count > 6 }
let passValid = password.map { [=10=].count > 6 }
let confirmValid = Observable.combineLatest(password, confirm)
.map { [=10=] == }
areValidFields = Observable.combineLatest([usernameValid, passValid, confirmValid])
}
}
在您的视图模型中,更喜欢在 init
函数中接受输入。如果你不能这样做,例如如果某些输入尚不存在,则使用 Subject 属性 并绑定到它。但是在任何一种情况下,您的视图模型基本上应该只包含一个初始化函数和一些输出属性。 DisposeBag
不会 进入视图模型。
您的视图控制器只需要创建一个视图模型并连接到它:
class LoginViewController: UIViewController {
@IBOutlet weak var textFieldUserName: UITextField!
@IBOutlet weak var textFieldPassword: UITextField!
@IBOutlet weak var textFieldConfirmPassword: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
let viewModel = LoginViewModel(
username: textFieldUserName.rx.text.orEmpty.asObservable(),
password: textFieldPassword.rx.text.orEmpty.asObservable(),
confirm: textFieldConfirmPassword.rx.text.orEmpty.asObservable()
)
let textFieldArray = [textFieldUserName!, textFieldPassword!, textFieldConfirmPassword!]
viewModel.areValidFields.subscribe(
onNext: { validArray in
for (field, valid) in zip(textFieldArray, validArray) {
if valid {
field.layer.borderColor = UIColor.clear.cgColor
}
else {
field.layer.borderColor = UIColor.red.cgColor
field.layer.borderWidth = 2.0
}
}
})
.disposed(by: bag)
}
private let bag = DisposeBag()
}
请注意,所有代码都在 viewDidLoad
函数中结束。这是理想情况,因此您不必处理 [weak self]
。在这种特殊情况下,我可能会将 onNext
闭包放在柯里化的全局函数中,在这种情况下它看起来像这样:
class LoginViewController: UIViewController {
@IBOutlet weak var textFieldUserName: UITextField!
@IBOutlet weak var textFieldPassword: UITextField!
@IBOutlet weak var textFieldConfirmPassword: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
let viewModel = LoginViewModel(
username: textFieldUserName.rx.text.orEmpty.asObservable(),
password: textFieldPassword.rx.text.orEmpty.asObservable(),
confirm: textFieldConfirmPassword.rx.text.orEmpty.asObservable()
)
let textFieldArray = [textFieldUserName!, textFieldPassword!, textFieldConfirmPassword!]
viewModel.areValidFields.subscribe(
onNext:update(fields: textFieldArray))
.disposed(by: bag)
}
private let bag = DisposeBag()
}
func update(fields: [UITextField]) -> ([Bool]) -> Void {
return { validArray in
for (field, valid) in zip(fields, validArray) {
if valid {
field.layer.borderColor = UIColor.clear.cgColor
}
else {
field.layer.borderColor = UIColor.red.cgColor
field.layer.borderWidth = 2.0
}
}
}
}
注意这里update(fields:)
函数在class中是而不是。这样我们就不会捕获自我,因此不必担心自我脆弱。此外,此更新功能可能对应用中的其他表单输入非常有用。