阅读 11138

RxSwift--我们为什么要用它

我本来是打算学习 Combine 的,但因为网上的资料太少,所以决定先学习 RxSwift 作为过渡。但是一学便爱上啦,声明式编程真是太有爱啦~

这是 RxSwift 中文学习文档(我的例子是来源于这里) https://beeth0ven.github.io/RxSwift-Chinese-Documentation/

需求

这是一个模拟用户登录的程序。

  • 当用户输入用户名时,如果用户名不足 5 个字就给出红色提示语,并且无法输入密码,当用户名符合要求时才可以输入密码。
  • 同样的当用户输入的密码不到 5 个字时也给出红色提示语。
  • 当用户名和密码有一个不符合要求时底部的绿色按钮不可点击,只有当用户名和密码同时有效时按钮才可点击。
  • 当点击绿色按钮后弹出一个提示框,这个提示框只是用来做演示而已。

声明式编程实现

override func viewDidLoad() {
        super.viewDidLoad()
        usernameValidOutlet.text = "Username has to be at least \(minimalUsernameLength) characters"
        passwordValidOutlet.text = "Password has to be at least \(minimalPasswordLength) characters"
        /*
         1. 输入用户名-》用户名是否有效-〉用户名提示是否隐藏/密码输入框是否可用
         */
        let usernameValid = usernameOutlet.rx.text.orEmpty
            .map { $0.count >= minimalUsernameLength }
            .share(replay: 1) // without this map would be executed once for each binding, rx is stateless by default
        usernameValid
            .bind(to: passwordOutlet.rx.isEnabled)
            .disposed(by: disposeBag)
        usernameValid
            .bind(to: usernameValidOutlet.rx.isHidden)
            .disposed(by: disposeBag)
        /*
         2. 输入密码-》密码是否有效-〉密码提示是否隐藏
         */
        let passwordValid = passwordOutlet.rx.text.orEmpty
           .map { $0.count >= minimalPasswordLength }
           .share(replay: 1)
        passwordValid
            .bind(to: passwordValidOutlet.rx.isHidden)
            .disposed(by: disposeBag)
        /*
         3. 密码是否有效/用户名是否有效-》登录按钮是否可用
         */
        let everythingValid = Observable
           .combineLatest(usernameValid, passwordValid) { $0 && $1 }
           .share(replay: 1)
        everythingValid
            .bind(to: doSomethingOutlet.rx.isEnabled)
            .disposed(by: disposeBag)
        doSomethingOutlet.rx.tap
            .subscribe(onNext: { [weak self] _ in self?.showAlert() })
            .disposed(by: disposeBag)
    }
复制代码

这分明就是把上面需求中的内容一条一条用 Swift 写了一遍而已呀!!!虽然例子简单,但是却让我体会到了领导的感觉。领导只需要说明要什么,不需要去费心思去考虑怎么做 😄

那再看看命令式编程怎么做

命令式编程实现

override func viewDidLoad() {
    super.viewDidLoad()
        //        usernameOutlet.delegate = self
        //        passwordOutlet.delegate = self
        usernameOutlet.addObserver(self, forKeyPath: "text", options: .new, context: nil)
        passwordOutlet.addObserver(self, forKeyPath: "text", options: .new, context: nil)
        doSomethingOutlet.addTarget(self, action: #selector(handleBtnAction(_:)), for: .touchUpInside)
    }
    
    @objc func handleBtnAction(_ btn: UIButton) {
        showAlert()
    }
    
    override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
        guard let obj = object as? UITextField, let change = change?[.newKey] as? String else {
            return
        }
        if obj == usernameOutlet {
            nameChanged(change)
        } else if obj == passwordOutlet {
            psdChanged(change)
        }
    }
    
    func nameChanged(_ name: String) {
        if name.count < minimalUsernameLength {
            usernameValidOutlet.isHidden = true
            passwordValidOutlet.isEnabled = false
            doSomethingOutlet.isEnabled = false
        } else {
            usernameValidOutlet.isHidden = false
            passwordValidOutlet.isEnabled = true
            // 第一种方法
//            doSomethingOutlet.isEnabled = (passwordOutlet.text?.count ?? 0 > minimalPasswordLength)
            // 第二种方法
            doSomethingOutlet.isEnabled = passwordValidOutlet.isHidden
        }
    }
    
    func psdChanged(_ psd: String) {
        if psd.count < minimalPasswordLength {
            passwordValidOutlet.isEnabled = false
            doSomethingOutlet.isEnabled = false
        } else {
            passwordValidOutlet.isEnabled = true
            doSomethingOutlet.isEnabled = true
        }
    }
//    func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
//
//    }
复制代码
  • 这段代码可能是有问题的,甚至编译都编译不起来的,因为我没有 build,只是为了说明命令式编程怎么做而已
  • 没有采用 UITextField 代理的方式是因为代理更麻烦,需要处理被注释掉的那个代理方法,而这个方法并不是直接把新的 text 值传进来的,需要我们自己根据输入框当前的值和被替换的信息自己得到,这很麻烦,所以我用了 KVO,无论行不行,意思到了就行
  • 在用户名输入框内容符合要求之后,要更新doSomethingOutlet按钮的状态,此时按钮状态取决于密码输入框内容是否符合要求,获取密码输入框内容是否符合要求有两种办法:
  • doSomethingOutlet.isEnabled = (passwordOutlet.text?.count ?? 0 > minimalPasswordLength)。这种方式不太好,需要把密码输入框是否符合要求的判断放在两个地方,而且这是个修改时容易忽略的地方
  • doSomethingOutlet.isEnabled = passwordValidOutlet.isHidden。这种方式从逻辑上讲更不合适,因为按钮是否可用居然跟 passwordValidOutlet 是否隐藏有关,在不知道需求的情况下,这真的是非常奇怪的写法

声明式编程的好处我觉得不是在于语法上有多方便,不在于把视图创建和事件的处理代码放在一处很方便,而是逻辑上很简单清晰。

例子虽然简单,但是我在写命令式实现的时候,还是需要停下来想一想 isEnabled 是 true 还是 false 等等。而命令式编程已经让我看到产品经理一旦理好需求,似乎可以直接上手写代码啦 😄

机器人代替程序员写代码指日可待呀 😄

文章分类
iOS
文章标签