RxSwift实战教程-核心用法

AshtonTrayl 7年前
   <p>前面两篇关于RxSwift的文章都是一些概念,我估计大伙看了一定是迷迷糊糊的,还是不知道RxSwift怎么使用,那么这里俺就带领大伙一起去做一个Demo,去实战一下RxSwift,大伙耐心写完,理解透彻以后,保证大伙能掌握到RxSwift基本核心用法。</p>    <p>掌握了这篇内容,再回头看下前面两篇文章,保证你会豁然开朗某些概念,对RxSwift掌握的更加深入。 这篇文章没有对线程过分强调,请求都会在当前线程完成,计划会在下篇文章中讲解对线程的区别 。</p>    <h2>Demo</h2>    <p>咱们这个Demo选用了最万能的登录注册功能,先来看下Demo的一些基本演示,并未包含所有细节,</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/49ceba468ea7e253d7a5b9eb289e962f.gif"></p>    <p style="text-align:center">demo.gif</p>    <h3>注册界面</h3>    <ul>     <li>输入用户名要大于6个字符,不然密码不能输入</li>     <li>密码必须大于6个字符,不然重复密码不能输入</li>     <li>重复密码输入必须和密码一样,不然注册按钮不能点击</li>     <li>点击注册按钮,提示注册失败或者注册成功</li>     <li>注册成功会写入本地的plist文件,然后输入用户名会检测用户名是否已经注册</li>    </ul>    <h3>登录界面</h3>    <ul>     <li>点击输入用户名,如果本地plist文件中没有注册过这个用户,会提示用户名不存在</li>     <li>点击输入用户名,如果本地plist文件中有注册过这个用户,会提示用户名可用</li>     <li>输入密码点击登录,如果密码错误提示密码错误</li>     <li>输入密码点击登录,如果密码正确则跳入列表界面,然后提示登录成功</li>    </ul>    <h3>列表界面</h3>    <ul>     <li>输入英雄的首字进行筛选。</li>    </ul>    <p>好了差不多就先搞这么些功能吧?什么你居然认为内容很多?放心东西很简单的,慢慢地参考着写呗,每天搞定一个界面就中了,技术妥妥的提升!</p>    <h3>用到的RxSwift概念</h3>    <ul>     <li>注册界面,我们使用Observale, Variable, Subject,bingTo等</li>     <li>登录界面,我们主要去使用Driver</li>     <li>列表界面,这个很简单,TableView打算后面单独写一篇,主要是简单的tableView展现和搜索</li>    </ul>    <p>demo是使用的纯MVVM模式,因为RxSwift就是为MVVM而生。不懂MVVM的童鞋请看 MVVM模式快速入门 ,我默认大家对MVVM有大致的了解。</p>    <p>另外demo使用了carthage引入的RxSwift和RxCocoa,当然你也可以使用cocoapods引入这些东西。具体怎么引入请大家看github介绍吧。</p>    <h2>let's go</h2>    <p>首先请大家建立一个新的Swift项目,然后把RxSwift和RxCocoa引入到项目中。为什么要引入RxCocoa?RxCocoa是对cocoa进行的Rx扩展,他已经包含了一个我们需要使用到的observable流,比如button的tap事件,已经帮我们包装成了一个observable流。一般做iOS开发的要使用到RxSwift都要用到RxCocoa的,这两个是相辅相成的。所有在所有的ViewController和ViewModel文件中引入这两个文件</p>    <pre>  <code class="language-swift">import RxCocoa  import RxSwift</code></pre>    <h2>登录界面</h2>    <p>这个界面我们主要学习使用Obserable和Subject的使用</p>    <p>大家先在storyboard上面建立好这个样子的界面</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/996f60fc0de9a504dda12fb0296a7478.jpg"></p>    <p>register.jpeg</p>    <h3>建立需要文件</h3>    <p>然后建立对应的RegisterViewController,他看起来应该是下面这样子</p>    <pre>  <code class="language-swift">class RegisterViewController: UIViewController {      @IBOutlet weak var usernameTextField: UITextField!      @IBOutlet weak var usernameLabel: UILabel!      @IBOutlet weak var passwordTextField: UITextField!      @IBOutlet weak var passwordLabel: UILabel!      @IBOutlet weak var repeatPasswordTextField: UITextField!      @IBOutlet weak var repeatPasswordLabel: UILabel!        @IBOutlet weak var registerButton: UIButton!      @IBOutlet weak var loginButton: UIBarButtonItem!        override func viewDidLoad() {          super.viewDidLoad()      }  }</code></pre>    <p>另外建立一个RegisterViewModel.swift文件,一个Protocol.swift文件,一个Service.swift文件</p>    <p>我们先写那个比较好呢?我比较习惯先写Service,我们就先写Service吧,service文件主要负责一些网路请求,和一些数据的访问操作。然后供ViewModel去使用。</p>    <p>首先我们在Service文件中创建ValidationService类,最好不要继承NSObject,Swift中推荐尽量使用原生类。我们需要考虑当文本框里面内容改变的时候,我们需要把传来的username进行处理,判断是否符合我们的条件,然后返回处理结果,也就是状态。我们在protocol.swift文件中使用一个枚举表示我们处理结果:所以我们在 <strong>protocol.swift</strong> 文件中添加下面内容</p>    <pre>  <code class="language-swift">// 表示我们的一些请求的结果  enum Result {      case ok(message: String)      case empty      case failed(message: String)  }</code></pre>    <h3>username的处理</h3>    <p>username的处理我加了一个已存在的判断,效果如下</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/3285e67b6283803c317b9ad50375b016.gif"></p>    <p style="text-align:center">cunzai.gif</p>    <ul>     <li>如果你已经注册过这个用户,那么会直接提示用户名已存在</li>    </ul>    <p>好了回到我们的 <strong>ValidationService</strong> 类中,我们写处理username的方法。他应该看起来是下面这样子。</p>    <pre>  <code class="language-swift">class ValidationService {      static let instance = ValidationService()        private init() {}        let minCharactersCount = 6        //这里面我们返回一个Observable对象,因为我们这个请求过程需要被监听。      func validateUsername(_ username: String) -> Observable<Result> {            if username.characters.count == 0 {//当字符等于0的时候什么都不做              return .just(.empty)          }            if username.characters.count < minCharactersCount {//当字符小于6的时候返回failed              return .just(.failed(message: "号码长度至少6个字符"))          }            if usernameValid(username) {//检测本地数据库中是否已经存在这个名字              return .just(.failed(message: "账户已存在"))          }            return .just(.ok(message: "用户名可用"))      }         // 从本地数据库中检测用户名是否已经存在      func usernameValid(_ username: String) -> Bool {          let filePath = NSHomeDirectory() + "/Documents/users.plist"          let userDic = NSDictionary(contentsOfFile: filePath)          let usernameArray = userDic!.allKeys as NSArray          if usernameArray.contains(username) {              return true          } else {              return false          }      }  }</code></pre>    <p>下面开始写我们的 <strong>RegisterViewModel.swift</strong> ,我们声明一个username他是一个Variable的对象,为什么是一个Variable对象呢?因为username既是一个observable也是一个observer,所以我们声明为他为一个Variable对象。我们对username进行处理的应该有一个结,结果应该是需要界面去监听来改变界面,因为处理的结果不需要是一个observer,所以我们把它声明为一个Observable<Result>类型。所以你的RegisterViewModel类应该是这样子。</p>    <pre>  <code class="language-swift">class RegisterViewModel {      //input:      let username = Variable<String>("")    //初始值为""        // output:      let usernameUsable: Observable<Result>        init() {        }  }</code></pre>    <p>然后我们在写我们的 <strong>RegisterViewController.swift</strong> 文件,viewDidLoad()看起来应该是下面这个样子</p>    <pre>  <code class="language-swift">let disposeBag = DisposeBag()    override func viewDidLoad() {      super.viewDidLoad()        let viewModel = RegisterViewModel()        usernameTextField.rx.text.orEmpty          .bindTo(viewModel.username)          .addDisposableTo(disposeBag)  }</code></pre>    <ul>     <li>其中 usernameTextField.rx.text.orEmpty 是RxCocoa库中的东西,他把TextFiled的text变成了一个Observable,后面的orEmpty我们可以Command点进去看下,他会把String?过滤nil帮我们变为String类型。</li>     <li>好了,因为我们的username既是一个observable也是一个observer,此时此刻我们把他当成一个Observer绑定到usernameTextFiled上,监听我们的usernameTextField流。绑定就是监听,bingTo里面里面的就是监听者也就是Observer</li>     <li>因为我们有监听,就要有监听资源的回收,所以我们创建一个disposeBag来盛放我们的这些监听资源。</li>    </ul>    <p>好了回到我们的 <strong>RegisterViewModel</strong> 类中,我们添加下面的代码</p>    <pre>  <code class="language-swift">init() {      let service = ValidationService.instance        usernameUsable = username.asObservable()          .flatMapLatest{ username in              return service.validateUsername(username)                  .observeOn(MainScheduler.instance)                  .catchErrorJustReturn(.failed(message: "username检测出错"))          }          .shareReplay(1)  }</code></pre>    <p>因为username是Variable类型,既可以当observer也可以当observable,viewModel中我们把它当成observable,然后对里面的元素进行监听和处理,这里面我们使用了flatMap,因为我们需要返回一个新的序列,也就是返回处理结果,因为涉及到数据库操作或者网络请求(当然是模拟的网络请求),所以这个序列需要我们去监听,这种情况我们使用flatMap(具体请参考)</p>    <p>后面使用.shareReplay(1)是因为我们要保证无论多少个Observer来监听我们这个序列,username的处理代码我们只执行一次,这一次请求结果供多有的observer去使用。</p>    <p>下面我们在 <strong>RegisterViewController</strong> 中处理我们的username请求结果。我们在ViewDidLoad中添加下列代码</p>    <pre>  <code class="language-swift">viewModel.usernameUsable      .bindTo(usernameLabel.rx.validationResult)      .addDisposableTo(disposeBag)  viewModel.usernameUsable      .bindTo(passwordTextField.rx.inputEnabled)      .addDisposableTo(disposeBag)</code></pre>    <ul>     <li>将ViewModel中username处理结果usernameUsable绑定到usernameLabel显示文案上,根据不同的结果显示不同的文案</li>     <li>将ViewModel中username处理结果usernameUsable绑定到passwordTextField是否可以输入,根据不同的结果判断password是否可以输入。</li>    </ul>    <p>上面的validationResult,inputEnabled需要我们自己去定制,这就又用到了 RxSwift入坑解读-那些难以理解的细节 文章中的UIBindingObserver了,我们需要创建自己的监听者。具体大家可以好好参考这篇文章。</p>    <p>所以我们在 protocol.swift 文件中添加下列代码</p>    <pre>  <code class="language-swift">extension Result {      var isValid: Bool {          switch self {          case .ok:              return true          default:              return false          }      }  }    extension Result {      var textColor: UIColor {          switch self {          case .ok:              return UIColor(red: 138.0 / 255.0, green: 221.0 / 255.0, blue: 109.0 / 255.0, alpha: 1.0)          case .empty:              return UIColor.black          case .failed:              return UIColor.red          }      }  }    extension Reactive where Base: UILabel {      var validationResult: UIBindingObserver<Base, Result> {          return UIBindingObserver(UIElement: base) { label, result in              label.textColor = result.textColor              label.text = result.description          }      }  }    extension Reactive where Base: UITextField {      var inputEnabled: UIBindingObserver<Base, Result> {          return UIBindingObserver(UIElement: base) { textFiled, result in              textFiled.isEnabled = result.isValid          }      }  }</code></pre>    <ul>     <li>我们首先对Result进行了扩展,添加了一个isValid属性,如果他的状态是ok,这个属性就返回true,否则就返回false</li>     <li>然后对Result添加了一个textColor属性,如果Result属性为ok的时候颜色就是绿色,否则即使红色</li>     <li>下面我们自定义了一个Observer,对UIlabel进行了扩展,根据result结果,进行他的text和textColor的显示</li>     <li>最后我们对UITextField进行扩展,根据result结果,进行他的isEnabled进行设置</li>    </ul>    <p>好了到了这里我们就可以运行项目来看下程序的运行情况,试着去输入username尝试一下吧。激动了不?</p>    <p>总结一下这个过程:</p>    <p>输入文本框Observable流->ViewModel中username对文本框进行监听->然后username调用service进行处理得到usernameUsable结果流->提示lable对usernameUsable进行监听刷新UI。</p>    <p>其实就是两个流的过程</p>    <p>UI操作->ViewModel->改变数据</p>    <p>数据改变->ViewModel->UI刷新</p>    <p>哈哈,这就是响应式编程,看起来是不是一路的监听啊</p>    <h3>password的处理</h3>    <p>有了上面的理解,对password的处理我们就轻车熟路了。很简单了,有些概念我就不需要解释太多了。</p>    <p>我们现在 <strong>Service</strong> 中添加对password的处理方法</p>    <pre>  <code class="language-swift">func validatePassword(_ password: String) -> Result {      if password.characters.count == 0 {          return .empty      }        if password.characters.count < minCharactersCount {          return .failed(message: "密码长度至少6个字符")      }        return .ok(message: "密码可用")  }    func validateRepeatedPassword(_ password: String, repeatedPasswordword: String) -> Result {      if repeatedPasswordword.characters.count == 0 {          return .empty      }        if repeatedPasswordword == password {          return .ok(message: "密码可用")      }        return .failed(message: "两次密码不一样")  }</code></pre>    <ul>     <li>validatePassword处理我们输入的password</li>     <li>validateRepeatedPassword处理我们的密码确认</li>     <li>上面的返回结果都是一个Result类型的值,因为我们外面不要对这个处理过程进行监听,所以不必返回一个新的序列</li>    </ul>    <p>然后我们在 <strong>RegisterViewModel.swift</strong> 文件中,添加需要的observale</p>    <pre>  <code class="language-swift">//input:  let password = Variable<String>("")  let repeatPassword = Variable<String>("")    //output:  let passwordUsable: Observable<Result>    //密码是否可用  let repeatPasswordUsable: Observable<Result> //密码确定是否正确</code></pre>    <p>然后我们在 <strong>RegisterViewController</strong> 中,添加passwordTextField绑定</p>    <pre>  <code class="language-swift">passwordTextField.rx.text.orEmpty      .bindTo(viewModel.password)      .addDisposableTo(disposeBag)  repeatPasswordTextField.rx.text.orEmpty      .bindTo(viewModel.repeatPassword)      .addDisposableTo(disposeBag)</code></pre>    <ul>     <li>将viewModel的password对passwordTextField进行监听</li>     <li>将viewModel的repeatPassword对repeatPasswordTextField进行监听</li>    </ul>    <p>然后再转移到 <strong>RegisterViewModel</strong> 中,处理我们的password的输入</p>    <pre>  <code class="language-swift">passwordUsable = password.asObservable()      .map { password in          return service.validatePassword(password)      }      .shareReplay(1)    repeatPasswordUsable = Observable.combineLatest(password.asObservable(), repeatPassword.asObservable()) {              return service.validateRepeatedPassword($0, repeatedPasswordword: $1)      }      .shareReplay(1)</code></pre>    <ul>     <li>这里使用的是map,因为处理密码不需要去联网操作,我们不需要对他进行监听处理,只需要将流中的每一个元素item转换为result的值。</li>     <li>下面对确定密码的处理,我们使用了一个 combineLatest 进行联合,也就是对两个流的item进行处理,返回处理结果流。</li>    </ul>    <p>下面转移到 <strong>RegisterViewController</strong> 中对ViewModel中的output进行处理</p>    <pre>  <code class="language-swift">viewModel.passwordUsable      .bindTo(passwordLabel.rx.validationResult)      .addDisposableTo(disposeBag)  viewModel.passwordUsable      .bindTo(repeatPasswordTextField.rx.inputEnabled)      .addDisposableTo(disposeBag)    viewModel.repeatPasswordUsable      .bindTo(repeatPasswordLabel.rx.validationResult)      .addDisposableTo(disposeBag)</code></pre>    <ul>     <li>passwordLabel对viewModel.passwordUsable进行监听,显示不同的文案提示</li>     <li>repeatPasswordTextField对passwordUsable进行监听,结果ok可输入状态,否则就是不可输入</li>     <li>repeatPasswordTextField对repeatPasswordUsable进行监听,显示不同的文案提示</li>    </ul>    <p>呼呼!好了运行下程序看看吧,输入password和确定密码感受下吧。</p>    <h3>注册按钮处理</h3>    <p>下面我们来这个界面的最后一个button处理吧,比较简单,贴贴代码解释解释,轻轻松松��</p>    <p>首先我们写service里面的注册方法</p>    <pre>  <code class="language-swift">func register(_ username: String, password: String) -> Observable<Result> {      let userDic = [username: password]        let filePath = NSHomeDirectory() + "/Documents/users.plist"        if (userDic as NSDictionary).write(toFile: filePath, atomically: true) {          return .just(.ok(message: "注册成功"))      }      return .just(.failed(message: "注册失败"))  }</code></pre>    <ul>     <li>我是直接把注册信息写入到本地的plist文件,写入成功就返回ok,否则就是failed</li>    </ul>    <p>然后我们来到 <strong>RegisterViewModel</strong> 文件中,添加需要的input和output</p>    <pre>  <code class="language-swift">//input:  let registerTaps = PublishSubject<Void>()    // output:  let registerButtonEnabled: Observable<Bool>  let registerResult: Observable<Result></code></pre>    <ul>     <li>registerTaps我们使用了PublishSubject,因为不需要有初试元素,其实前面的Variable都可以换成PublishSubject。大伙可以试试</li>     <li>下面就是注册按钮是否可用的输出,这个其实关系到username和password</li>     <li>最后就是注册结果的output</li>    </ul>    <p>进入 <strong>RegisterViewController</strong> 中,我们添加输入绑定</p>    <pre>  <code class="language-swift">registerButton.rx.tap      .bindTo(viewModel.registerTaps)      .addDisposableTo(disposeBag)</code></pre>    <p>然后回到 <strong>RegisterViewModel</strong> 文件中,对button的点击输入进行处理,我们添加下面这些代码,然后我一点点解释</p>    <pre>  <code class="language-swift">registerButtonEnabled = Observable.combineLatest(usernameUsable, passwordUsable, repeatPasswordUsable) {      (username, password, repeatPassword) in          username.isValid && password.isValid && repeatPassword.isValid      }      .distinctUntilChanged()      .shareReplay(1)    let usernameAndPassword = Observable.combineLatest(username.asObservable(), password.asObservable()) {      ($0, $1)  }    registerResult = registerTaps.asObservable().withLatestFrom(usernameAndPassword)      .flatMapLatest { (username, password) in          return service.register(username, password: password)              .observeOn(MainScheduler.instance)              .catchErrorJustReturn(.failed(message: "注册出错"))      }      .shareReplay(1)</code></pre>    <ul>     <li>首先registerButtonEnabled的处理,把username,password和repeatePassword的处理结果绑定到一起,返回一个总的结果流,是一个bool值的流</li>     <li>我们现将username和password进行结合,得到一个元素是他俩组合的元组的流</li>     <li>然后对registerTaps事件进行监听,我们拿到每一个元组进行注册行为,涉及到耗时数据库操作,我们需要对这个过程进行监听,所以我们使用flatMap函数,返回一个新的流,</li>    </ul>    <p>然后回到 <strong>RegisterViewController</strong> 文件中,我们对ViewModel的output进行处理,你需要添加以下代码</p>    <pre>  <code class="language-swift">viewModel.registerButtonEnabled      .subscribe(onNext: { [unowned self] valid in          self.registerButton.isEnabled = valid          self.registerButton.alpha = valid ? 1.0 : 0.5      })      .addDisposableTo(disposeBag)    viewModel.registerResult      .subscribe(onNext: { [unowned self] result in          switch result {          case let .ok(message):              self.showAlert(message: message)          case .empty:              self.showAlert(message: "")          case let .failed(message):              self.showAlert(message: message)          }      })      .addDisposableTo(disposeBag)</code></pre>    <ul>     <li>对registerButtonEnabled进行监听,根据不同的item对注册按钮进行设置</li>     <li>对registerResult进行监听,显示不同的弹框信息</li>    </ul>    <p>弹框方法</p>    <pre>  <code class="language-swift">func showAlert(message: String) {      let action = UIAlertAction(title: "确定", style: .default, handler: nil)      let alertViewController = UIAlertController(title: nil, message: message, preferredStyle: .alert)      alertViewController.addAction(action)      present(alertViewController, animated: true, completion: nil)  }</code></pre>    <p>注册界面终于OK了,希望大家写完好好思考一下流程哦</p>    <h2>注册界面</h2>    <p>这里面我们主要学习使用Driver的使用</p>    <p>其实Driver和Observable的使用结构是一样的只是Driver和Observable有点区别,Driver是RxSwift专门针对UI操作,而Observable是一个通用的东西,他们的区别可以参考我的p <a href="/misc/goto?guid=4959728776228977219" rel="nofollow,noindex">上一篇</a> 文章中的解析</p>    <p>首先我们在StoryBoard添加登录界面,如下,当点击登录的时候,跳转到我们的登录界面</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/7c3af6dc0aa41ff5a2c83b8834fbec64.jpg"></p>    <p style="text-align:center">login.jpeg</p>    <p>我们仍然建立 <strong>LoginViewController.swift</strong> 和 <strong>LoginViewModel.swift</strong> 文件。</p>    <p>有了上面注册功能的一些代码工具,我们这边讲解就会比较轻松一点了。</p>    <p>首先在 <strong>service</strong> 中ValidationService添加下面的代码</p>    <pre>  <code class="language-swift">func loginUsernameValid(_ username: String) -> Observable<Result> {      if username.characters.count == 0 {          return .just(.empty)      }        if usernameValid(username) {          return .just(.ok(message: "用户名可用"))      }      return .just(.failed(message: "用户名不存在"))  }    func login(_ username: String, password: String) -> Observable<Result> {      let filePath = NSHomeDirectory() + "/Documents/users.plist"      let userDic = NSDictionary(contentsOfFile: filePath)      if let userPass = userDic?.object(forKey: username) as? String {          if  userPass == password {              return .just(.ok(message: "登录成功"))          }      }      return .just(.failed(message: "密码错误"))  }</code></pre>    <ul>     <li>判断用户名是否可用,如果本地plist文件中有这个用户名,就表示可以使用这个用户名登录,用户名可用</li>     <li>登录方法,如果usernmae和password都正确的话,就是登录成功,否则就是密码错误了</li>    </ul>    <p>然后是 <strong>LoginViewModel.swift</strong> ,我们写成下面这样子</p>    <pre>  <code class="language-swift">class LoginViewModel {      // output:      let usernameUsable: Driver<Result>      let loginButtonEnabled: Driver<Bool>      let loginResult: Driver<Result>        init(input: (username: Driver<String>, password: Driver<String>, loginTaps: Driver<Void>),          service: ValidationService) {          usernameUsable = input.username              .flatMapLatest { username in                      return service.loginUsernameValid(username)                          .asDriver(onErrorJustReturn: .failed(message: "连接server失败"))              }            let usernameAndPassword = Driver.combineLatest(input.username, input.password) {              ($0, $1)          }            loginResult = input.loginTaps.withLatestFrom(usernameAndPassword)              .flatMapLatest { (username, password) in                  return service.login(username, password: password)                      .asDriver(onErrorJustReturn: .failed(message: "连接server失败"))              }            loginButtonEnabled = input.password              .map { $0.characters.count > 0 }              .asDriver()      }  }</code></pre>    <ul>     <li>首先我们声明的output都是Driver类型的,第一个是username处理结果流,第二个是登录按钮是否可用的流,第三个是登录结果流</li>     <li>下面的init方法,看着和刚才的注册界面不一样。这种写法我参考了官方文档的写法,让大家知道有这种写法。但是我并不推荐大家使用这种方式,因为如果Controller中的元素很多的话,一个一个传过来是很可笑的把。</li>     <li>初始化方法传入的是一个input元组,包括username的Driver序列,password的Driver序列,还有登录按钮点击的Driver序列,还有Service对象,需要Controller传递过来,其实Controller不应该拥有Service对象。</li>     <li>初始化方法中,我们对传入的序列进行处理和转换成相对应的output序列。</li>     <li>大家看到了使用了Driver,我们不再需要shareReplay(1)</li>     <li>明白了注册界面的东西,这些东西也自然很简单了。</li>    </ul>    <p>下面我们在 <strong>LoginViewController.swift</strong> 中添加下列代码</p>    <pre>  <code class="language-swift">override func viewDidLoad() {      super.viewDidLoad()        viewModel = LoginViewModel(input: (username: usernameTextField.rx.text.orEmpty.asDriver(),                                             password: passwordTextField.rx.text.orEmpty.asDriver(),                                             loginTaps: loginButton.rx.tap.asDriver()),                                     service: ValidationService.instance)        viewModel.usernameUsable          .drive(usernameLabel.rx.validationResult)          .addDisposableTo(disposeBag)        viewModel.loginButtonEnabled          .drive(onNext: { [unowned self] valid in              self.loginButton.isEnabled = valid              self.loginButton.alpha = valid ? 1 : 0.5          })          .addDisposableTo(disposeBag)        viewModel.loginResult          .drive(onNext: { [unowned self] result in              switch result {              case let .ok(message):                  self.performSegue(withIdentifier: "container", sender: self)                  self.showAlert(message: message)              case .empty:                  self.showAlert(message: "")              case let .failed(message):                  self.showAlert(message: message)              }          })          .addDisposableTo(disposeBag)   }</code></pre>    <ul>     <li>我们给viewModel传入相应的input的Driver序列</li>     <li>将viewModel中的output进行相应的监听,如果是Driver序列,我们这里不使用bingTo,而是使用的Driver,用法和bingTo一模一样。</li>     <li>Deriver的监听一定发生在主线程,所以很适合我们更新UI的操作</li>     <li>登录成功会跳转到我们的列表界面</li>    </ul>    <p>哈哈,我们已经完成了两个界面的编写了,相信你对RxSwift已经有了一个全新的认识了吧。</p>    <h2>列表界面</h2>    <p>下面是列表界面,限于篇幅的原因,这里我只写展现,具体的搜索功能和其他tableView的展现技术,我会在下篇文章中进行分享。</p>    <p>在StoryBoard文件中新建列表界面</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/d496f23cb43bd12cbc918461ed7fb600.jpg"></p>    <p style="text-align:center">container.jpeg</p>    <p>然后建立相应的ContainerViewController.swift和ContainerViewModel.swift文件。和Hero.swift文件。将所需的图片资源导入,下载Demo,Demo中有。</p>    <p>首先编写我们 <strong>Hero</strong> 类</p>    <pre>  <code class="language-swift">class Hero: NSObject {      var name: String      var desc: String      var icon: String        init(name: String, desc: String, icon: String) {          self.name = name          self.desc = desc          self.icon = icon      }  }</code></pre>    <p>然后我们在 <strong>Service</strong> 文件中添加一个新的Service类,或者在原来的类中添加方法</p>    <pre>  <code class="language-swift">class SearchService {      static let shareInstance = SearchService()        private init() {}        func getHeros() -> Observable<[Hero]> {          let herosString = Bundle.main.path(forResource: "heros", ofType: "plist")          let herosArray = NSArray(contentsOfFile: herosString!) as! Array<[String: String]>          var heros = [Hero]()          for heroDic in herosArray {              let hero = Hero(name: heroDic["name"]!, desc: heroDic["intro"]!, icon: heroDic["icon"]!)              heros.append(hero)          }            return Observable.just(heros)                      .observeOn(MainScheduler.instance)      }    }</code></pre>    <ul>     <li>从本地拉去数据,然后转换成Hero模型</li>     <li>我们返回的是一个元素是Hero数组的Observable流。接下来更新UI的操作要在主线程中</li>    </ul>    <p>然后看下我们的 <strong>ContainerViewModel</strong></p>    <pre>  <code class="language-swift">class ContainerViewModel {      // output:      var models: Driver<[Hero]>        init(withSearchText searchText: Observable<String>, service: SearchService) {          models = searchText              .debug()              .observeOn(ConcurrentDispatchQueueScheduler(qos: .background))              .flatMap { text in                  return service.getHeros(withName: text)              }.asDriver(onErrorJustReturn: [])      }  }</code></pre>    <ul>     <li>我们的output是一个Driver流,因为更新tableView是UI操作</li>     <li>然后我们使用service拉去数据的操作应该是在后台线程去运行,所以添加了observeOn操作</li>     <li>然后使用flatMap返回新的Observable流,转换成output的Driver流</li>    </ul>    <p>我们的 <strong>ContainerViewController</strong> 类就很简单了</p>    <pre>  <code class="language-swift">override func viewDidLoad() {      super.viewDidLoad()      let viewModel = ContainerViewModel(withSearchText: searchBarText, service: SearchService.shareInstance)        viewModel.models          .drive(tableView.rx.items(cellIdentifier: "cell", cellType: UITableViewCell.self)) { (row, element, cell) in              cell.textLabel?.text = element.name              cell.detailTextLabel?.text = element.desc              cell.imageView?.image = UIImage(named: element.icon)      }      .addDisposableTo(disposeBag)     }</code></pre>    <ul>     <li>这里我们不需要设置dataSource</li>     <li>将数据绑定到tableView的items元素,这是RxCocoa对tableView的一个扩展方法。</li>     <li>其实MVVM模式中,model层不应该暴露给ViewController,这里我们这简单处理了下,不必在意这些细节:grimacing:</li>    </ul>    <p>我们点进去可以看下,一共有三个items方法,并且上面都有些使用方法,我们使用的这个是</p>    <pre>  <code class="language-swift">public func items<S : Sequence, Cell : UITableViewCell, O : ObservableType where O.E == S>(cellIdentifier: String, cellType: Cell.Type = default) -> (O) -> (@escaping (Int, S.Iterator.Element, Cell) -> Swift.Void) -> Disposable</code></pre>    <p>这是一个科里化的方法,不带section的时候使用这个,他有两个参数,一个是循环利用的cell的identifier,一个cell的类型。后面会返回的是一个闭包,在闭包里对cell进行设置。方法用起来比较简单,就是有点难理解。</p>    <p> </p>    <p> </p>    <p> </p>    <p>来自:http://www.jianshu.com/p/089ae5bececa</p>    <p> </p>