簡體   English   中英

如何在ViewModel中放置可觀察到的RxSwift

[英]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])
        }
    }
}

使用此代碼,當我移回導航堆棧時,將打印出已處理的消息。

但是,如果我繼續前進,則不會打印已處置的消息。 處理可觀察對象的正確方法是什么?

向前移動時,不會從堆棧中刪除視圖控制器。 它保留下來,以便當用戶點擊“后退”按鈕時,它已經准備好,並且仍處於與用戶上次看到它相同的狀態。 這就是為什么什么也沒有處置的原因。

同樣,由於您說過您仍在學習Rx,因此您所擁有的並不是最佳實踐。 我希望看到更多這樣的東西:

class LoginViewModel {

    let areValidFields: Observable<[Bool]>

    init(username: Observable<String>, password: Observable<String>, confirm: Observable<String>) {

        let usernameValid = username.map { $0.count > 6 }
        let passValid = password.map { $0.count > 6 }
        let confirmValid = Observable.combineLatest(password, confirm)
            .map { $0 == $1 }

        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:)函數不在類中。 這樣,我們就不會捕獲自我,因此不必擔心脆弱的自我。 另外,此更新功能對於應用程序中的其他表單輸入可能非常有用。

您已將一次性用品添加到LoginViewModel對象的處理包中,該對象在釋放LoginViewController對象時被釋放。
這意味着在釋放LoginViewController或您收到areValidFields Observable上的完成或錯誤之前,將不會處理LoginViewModel observable返回的一次性areValidFields

在大多數可觀察的情況下,這與公認的行為是同步的。

但是,如果您要在LoginViewController移出屏幕時處置可觀察對象,則可以手動處置:

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()
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM