📂
RxSwift
  • RxSwift 中文文档
  • 1. 为什么要使用 RxSwift?
  • 2. 你好 RxSwift!
  • 3. 函数响应式编程
    • 3.1 函数式编程
    • 3.2 函数式编程 -> 函数响应式编程
    • 3.3 数据绑定
  • 4. RxSwift 核心
    • 4.1 Observable - 可监听序列
      • Single
      • Completable
      • Maybe
      • Driver
      • Signal
      • ControlEvent
    • 4.2 Observer - 观察者
      • AnyObserver
      • Binder
    • 4.3 Observable & Observer 既是可监听序列也是观察者
      • AsyncSubject
      • PublishSubject
      • ReplaySubject
      • BehaviorSubject
      • Variable(已弃用)
      • ControlProperty
    • 4.4 Operator - 操作符
    • 4.5 Disposable - 可被清除的资源
    • 4.6 Schedulers - 调度器
    • 4.7 Error Handling - 错误处理
  • 5. 如何选择操作符?
    • amb
    • buffer
    • catchError
    • combineLatest
    • concat
    • concatMap
    • connect
    • create
    • debounce
    • debug
    • deferred
    • delay
    • delaySubscription
    • dematerialize
    • distinctUntilChanged
    • do
    • elementAt
    • empty
    • error
    • filter
    • flatMap
    • flatMapLatest
    • from
    • groupBy
    • ignoreElements
    • interval
    • just
    • map
    • merge
    • materialize
    • never
    • observeOn
    • publish
    • reduce
    • refCount
    • repeatElement
    • replay
    • retry
    • sample
    • scan
    • shareReplay
    • single
    • skip
    • skipUntil
    • skipWhile
    • startWith
    • subscribeOn
    • take
    • takeLast
    • takeUntil
    • takeWhile
    • timeout
    • timer
    • using
    • window
    • withLatestFrom
    • zip
  • 6. 更多示例
    • ImagePicker - 图片选择器
    • TableViewSectionedViewController - 多层级的列表页
    • Calculator - 计算器
  • 7. RxSwift 常用架构
    • 7.1 MVVM
      • Github Signup(示例)
    • 7.2 RxFeedback
      • Github Search(示例)
    • 7.3 ReactorKit
      • Github Search(示例)
  • 8. RxSwift 生态系统
  • 9. 学习资源
  • 10. 关于本文档
    • 10.1 文档更新日志
  • 食谱
    • RxSwift 5 更新了什么?
    • RxRelay
    • 纯函数
    • 附加作用
    • 共享附加作用
Powered by GitBook
On this page
  • 我的第一个 RxSwift 应用程序 - 输入验证:
  • 这个页面主要由 5 各元素组成:
  • 这里需要完成 4 个交互:
  • 更多疑问

Was this helpful?

2. 你好 RxSwift!

Previous1. 为什么要使用 RxSwift?Next3. 函数响应式编程

Last updated 4 years ago

Was this helpful?

我的第一个 RxSwift 应用程序 - 输入验证:

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

  • 当用户输入用户名时,如果用户名不足 5 个字就给出红色提示语,并且无法输入密码,当用户名符合要求时才可以输入密码。

  • 同样的当用户输入的密码不到 5 个字时也给出红色提示语。

  • 当用户名和密码有一个不符合要求时底部的绿色按钮不可点击,只有当用户名和密码同时有效时按钮才可点击。

  • 当点击绿色按钮后弹出一个提示框,这个提示框只是用来做演示而已。

你可以下载并在模拟器上运行,这样可以帮助于你理解整个程序的交互:

这个页面主要由 5 各元素组成:

  1. 用户名输入框

  2. 用户名提示语(红色)

  3. 密码输入框

  4. 密码提示语(红色)

  5. 操作按钮(绿色)

class SimpleValidationViewController : ViewController {

    @IBOutlet weak var usernameOutlet: UITextField!
    @IBOutlet weak var usernameValidOutlet: UILabel!

    @IBOutlet weak var passwordOutlet: UITextField!
    @IBOutlet weak var passwordValidOutlet: UILabel!

    @IBOutlet weak var doSomethingOutlet: UIButton!
    ...
}

这里需要完成 4 个交互:

  • 当用户名输入不到 5 个字时显示提示语,并且无法输入密码

    override func viewDidLoad() {
        super.viewDidLoad()
    
        ...
    
        // 用户名是否有效
        let usernameValid = usernameOutlet.rx.text.orEmpty
            // 用户名 -> 用户名是否有效
            .map { $0.count >= minimalUsernameLength }
            .share(replay: 1)
    
        ...
    
        // 用户名是否有效 -> 密码输入框是否可用
        usernameValid
            .bind(to: passwordOutlet.rx.isEnabled)  
            .disposed(by: disposeBag)
    
        // 用户名是否有效 -> 用户名提示语是否隐藏
        usernameValid
            .bind(to: usernameValidOutlet.rx.isHidden)
            .disposed(by: disposeBag)
    
        ...
    }

    当用户修改用户名输入框的内容时就会产生一个新的用户名, 然后通过 map 方法将它转化成用户名是否有效, 最后通过 bind(to: ...) 来决定密码输入框是否可用以及提示语是否隐藏。

  • 当密码输入不到 5 个字时显示提示文字

    override func viewDidLoad() {
        super.viewDidLoad()
    
        ...
    
        // 密码是否有效
        let passwordValid = passwordOutlet.rx.text.orEmpty
            // 密码 -> 密码是否有效
            .map { $0.count >= minimalPasswordLength }
            .share(replay: 1)
    
        ...
    
        // 密码是否有效 -> 密码提示语是否隐藏
        passwordValid
            .bind(to: passwordValidOutlet.rx.isHidden)
            .disposed(by: disposeBag)
    
        ...
    }

    这个和用用户名来控制提示语的逻辑是一样的。

  • 当用户名和密码都符合要求时,绿色按钮才可点击

    override func viewDidLoad() {
        super.viewDidLoad()
    
        ...
    
        // 用户名是否有效
        let usernameValid = ...
    
        // 密码是否有效
        let passwordValid = ...
    
        ...
    
        // 所有输入是否有效
        let everythingValid = Observable.combineLatest(
              usernameValid,
              passwordValid
            ) { $0 && $1 } // 取用户名和密码同时有效
            .share(replay: 1)
    
        ...
    
        // 所有输入是否有效 -> 绿色按钮是否可点击
        everythingValid
            .bind(to: doSomethingOutlet.rx.isEnabled)
            .disposed(by: disposeBag)
    
        ...
    }

    通过 Observable.combineLatest(...) { ... } 来将用户名是否有效以及密码是都有效合并出两者是否同时有效,然后用它来控制绿色按钮是否可点击。

  • 点击绿色按钮后,弹出一个提示框

    override func viewDidLoad() {
        super.viewDidLoad()
    
        ...
    
        // 点击绿色按钮 -> 弹出提示框
        doSomethingOutlet.rx.tap
            .subscribe(onNext: { [weak self] in self?.showAlert() })
            .disposed(by: disposeBag)
    }
    
    func showAlert() {
        let alertView = UIAlertView(
            title: "RxExample",
            message: "This is wonderful",
            delegate: nil,
            cancelButtonTitle: "OK"
        )
    
        alertView.show()
    }

    在点击绿色按钮后,弹出一个提示框

这样 4 个交互都完成了,现在我们纵观全局看下这个程序是一个什么样的结构:

然后看一下完整的代码:

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"

    let usernameValid = usernameOutlet.rx.text.orEmpty
        .map { $0.count >= minimalUsernameLength }
        .share(replay: 1)

    let passwordValid = passwordOutlet.rx.text.orEmpty
        .map { $0.count >= minimalPasswordLength }
        .share(replay: 1)

    let everythingValid = Observable.combineLatest(
          usernameValid,
          passwordValid
        ) { $0 && $1 }
        .share(replay: 1)

    usernameValid
        .bind(to: passwordOutlet.rx.isEnabled)
        .disposed(by: disposeBag)

    usernameValid
        .bind(to: usernameValidOutlet.rx.isHidden)
        .disposed(by: disposeBag)

    passwordValid
        .bind(to: passwordValidOutlet.rx.isHidden)
        .disposed(by: disposeBag)

    everythingValid
        .bind(to: doSomethingOutlet.rx.isEnabled)
        .disposed(by: disposeBag)

    doSomethingOutlet.rx.tap
        .subscribe(onNext: { [weak self] in self?.showAlert() })
        .disposed(by: disposeBag)
}

func showAlert() {
    let alertView = UIAlertView(
        title: "RxExample",
        message: "This is wonderful",
        delegate: nil,
        cancelButtonTitle: "OK"
    )

    alertView.show()
}

你会发现你可以用几行代码完成如此复杂的交互。这可以大大提升我们的开发效率。

更多疑问

  • share(replay: 1) 是用来做什么的?

  • disposed(by: disposeBag) 是用来做什么的?

我们用 usernameValid 来控制用户名提示语是否隐藏以及密码输入框是否可用。 就是让他们共享这一个源,而不是为他们单独创建新的源。这样可以减少不必要的开支。

和我们所熟悉的对象一样,每一个绑定也是有生命周期的。并且这个绑定是可以被清除的。disposed(by: disposeBag)就是将绑定的生命周期交给 disposeBag 来管理。当 disposeBag 被释放的时候,那么里面尚未清除的绑定也就被清除了。这就相当于是在用 来管理绑定的生命周期。 这个内容会在 章节详细介绍。

shareReplay
ARC
Disposable
这个例子