Rx中的反应式编程

首先,我当然建议您阅读官方文档。 在那里,您可以找到RxSwift运算符的基础知识。

本质上,在RxSwift中,所有事物都是围绕可观察对象构造的。 根据其名称,您可能会猜测我们将在整个程序中观察到此类对象的变化。 特别是,这些对象将通知您有关其状态的更改。 此外,我们拥有执行任何必要动作的能力,这些动作将改变返回的对象(过滤器,地图,flatMap等)。

因此,完整的工作流程如下所示:使用某些功能创建一个可观察对象。 例如,一个内部包含单个参数并返回可观察对象的对象。 此外,还有大量其他变体。 您可以在文档中检查它们。

使用不同的运算符(如果状态已更改)描述其行为。 例如,为了在特定条件下获取结果而进行过滤,或者设置一个线程来运行结果代码。 为每个状态(下一步,完成,错误)设置处理程序(如果需要)。 订阅-从现在开始通知观察者我们希望获得其通知。

此代码说明了订阅某些可观察对象的示例。 魔鬼并不像他画的那么黑,所以不要害怕。 该示例非常简单,因此我们将尝试逐步解释它。 您可以在文档中轻松找到所有未知方法。 首先,我们将创建一个OperationQueue。 这个阶段对您来说并不难。 现在,让我们深入探讨其他复杂的问题。

BackgroundWorkScheduler描述了用于从队列的后台执行操作的线程。 下一部分很有趣。 有些对象可以上传视频,我们将订阅其更改。

应该在后台线程上进行工作的设置,我们将在该线程上处理初始结果。

  • 使用“ map”方法转换对象以使其成为字符串。
  • 在主线程中开始工作,以进行UI工作
  • “设置”,处理下一个状态的功能。 实际上,我们在下面的几行中执行了所有这些操作。
  让operationQueue = NSOperationQueue()   
    operationQueue.maxConcurrentOperationCount = 3 
    operationQueue.qualityOfService = NSQualityOfService.UserInitiated 
    让backgroundWorkScheduler 
      = OperationQueueScheduler(operationQueue:operationQueue) 

  videoUpload   
    .observeOn(backgroundWorkScheduler) 
    .map({json in 
      返回json [“ videoUrl”]。stringValue 
    }) 
    .observeOn(MainScheduler.sharedInstance) 
    .subscribeNext {网址 
      self.urlLabel.text =网址 
    } 

此外,我们可以构建自己的可观察对象。 我们所需要的只是描述它的行为方式。

以下示例是关于对象创建的。 我们从Realm公司网站上的RxSwift教程中获取了它。 这里的主要思想是将请求组成一些服务器。 从我的角度来看,这是一个相当常见的任务。 幸运的是,我们可以显示其中的所有RX功率。

  创建{(观察者:AnyObserver)->一次性 

    让request = MyAPI.get(URL,(((result,error)-> { 
      如果让err =错误{ 
        rator.onError(err); 
      } 
      否则,如果让authResponse =结果{ 
        rator.onNext(authResponse); 
        rator.onComplete(); 
      } 
    }) 
    返回AnonymousDisposable { 
      request.cancel() 
    } 
  } 

根据前面的段落,可以很容易地构思出此代码。 但是,让我们弄清楚它。

“创建”功能构造一个可观察对象,该对象将“ GET”请求发送到服务器。 在代码主体中,您会注意到三种常见的RX状态。 如果出现问题,我们将向观察者发送“错误”状态。 否则,如果得到响应,我们将分派“下一步”,表明我们已更改状态。 最后,我们发送“完成”,表示我们已完成工作。

最后但并非最不重要的是AnonymousDisposable。 我们使用一次性物品退订当前对象。 通过创建它们,我们描述了取消订阅后应执行的操作。 同样,在前面的示例中,当我们没有任何观察者时,我们很容易取消请求。 看起来和没有反应一样吗? 让我们检查一下这一小段代码可以带来什么好处。

我们通常必须取消相同的请求(如果存在),以避免重复并提高应用程序的速度。 在这种情况下,我们需要在请求完成后调用存储完成块。 通常,我们通过将块存储在数组中,然后将其存储在带键的字典中来执行此操作,这是唯一的并且与我们的请求有关。 看起来很难完成这么简单的任务。

我们实际上可以从RX中受益吗? 订阅我们新对象的任何对象都将被通知完成。 结果,不需要任何其他数据存储。 方便简单。 不够?

此外,让我们考虑另一个问题-“块中的数据”。 您可能已经注意到,我们在“下一步”状态之后立即发送“完成”。 原因很明显-我们收到了所有必要的数据(已经描述过)。 如果我们没有足够的信息,我们只发送“ Next”而不发送“ Completed”。 “完成”选项应与最后一条数据一起发送。 订户要做的就是在他获得“下一个”时收集数据,并在获得“已完成”的情况下开始使用它。 很简单,不是吗? 我想您可能会发现这段代码很简单,所以只需像在这些示例中一样进行一些修改即可。

在下一节中,我将演示一些RxSwift运算符。 如前所述,您可以在RxSwift文档中查看整个列表。

可观察的
将您的对象转换为可观察对象(前提是可以转换您的对象)。

  设变量=变量(0) 

      variable.asObservable()。subscribe {e in 
          打印(e) 
      } 
      variable.value = 1 
  下一个(0)   
  下一个(1) 

创建
该运算符使用其自身的逻辑来启用可观察对象的完全创建。 我们在前面的示例中应用了它。

  让firstSequence = Observable.of(1、2、3)   
      让secondSequence = Observable.of(“ A”,“ B”,“ C”) 

      让multipleSequence = Observable> .create { 
          viewer.on(.Next(firstSequence)) 
          viewer.on(.Next(secondSequence)) 
          返回NopDisposable.instance 
      } 
      让concatSequence = multipleSequence.concat() 
      concatSequence.subscribe {e in 
          打印(e) 
      } 
  下一个(1)   
  下一个(2)   
  下一个(3)   
  下一个(A)   
  下一个(B)   
  下一个(C) 

递延
有机会删除可观察对象创建过程的时间,直到订阅它为止。

  var i = 1 
      let deferredJustObservable = Observable.deferred { 
          Observable.just(i) 
      } 
      我= 2 
      _ = deferredJustObservable.subscribeNext {打印(“ i = \($ 0)”)} 
  我= 2 

上面列出的运营商是目前使用最广泛的。 需要明确的是,RxSwift还具有与不断变化的UI配合使用的类似物,例如Rxtext,Rxenabled等。RxSwift及其UI表示形式之间的唯一区别是它是可观察的类型,您可以订阅它们以进行检查这些属性何时会改变。

最后,我附上了以下示例,以向您展示如何在RxSwift中使用UI。 尽管我不会简单地描述它,但我将比较用RxSwift编写的代码和标准的Apple代码方法,以强调其好处。

以下程序从文本字段中添加三个数字,并将结果存储在标签中。

  进口基金会   
  导入UIKit   
  #if!RX_NO_MODULE 
  导入RxSwift   
  进口RxCocoa   
  #万一 

  类NumbersViewController:ViewController {   
      @IBOutlet弱var number1:UITextField! 
      @IBOutlet弱var number2:UITextField! 
      @IBOutlet弱var数字3:UITextField! 

      @IBOutlet弱var结果:UILabel! 

      覆盖func viewDidLoad(){ 
          super.viewDidLoad() 

          Observable.combineLatest(number1.rx_text,number2.rx_text,number3.rx_text){textValue1,textValue2,textValue3-> Int in 
                  return(Int(textValue1)?? 0)+(Int(textValue2)?? 0)+(Int(textValue3)?? 0) 
              } 
              .map {$ 0.description} 
              .bindTo(result.rx_text) 
              .addDisposableTo(disposeBag) 
      } 
  } 

下一个示例说明了标准方法。

  进口基金会   
  导入UIKit 

  类RealNumbersViewController:ViewController { 

      @IBOutlet弱var数字3:UITextField!  { 
          didSet { 
              self.connectTextField(self.number3) 
          } 
      } 
      @IBOutlet弱var number1:UITextField!  { 
          didSet { 
              self.connectTextField(self.number1) 
          } 
      } 
      @IBOutlet弱var number2:UITextField!  { 
          didSet { 
              self.connectTextField(self.number2) 
          } 
      } 

      @IBOutlet弱var结果:UILabel! 


      覆盖func viewDidLoad(){ 
          self.calculateResultForTextField(number1) 
          self.calculateResultForTextField(number2) 
          self.calculateResultForTextField(number3) 
      } 

      func textFieldDidChange(textField:UITextField){ 
          self.calculateResultForTextField(textField) 
      } 

      私人功能connectTextField(textField:UITextField){ 
          textField.addTarget(self,action:#selector(RealNumbersViewController.textFieldDidChange(_ :)),forControlEvents:UIControlEvents.EditingChanged) 
      } 

      私有函数calculateResultForTextField(textField:UITextField){ 
          // //让calculatedValue =(Int(number1.text ?? 0)?? 0)+(Int(number2.text ?? 0)?? 0)+(Int(number3.text ?? 0)?? 0); 
          让firstOperand =(Int(textField.text ??“ 0”)?? 0) 
          让secondOperand =(Int(self.result.text ??“ 0”)?? 0) 
          self.result.text =(firstOperand + secondOperand).description 
      } 
  } 

如您所见,编写的代码量存在巨大差异。 RX方法可以消除不必要的条件。 我什至尝试通过覆盖设置器来使用React,但是仍然不能比完整的Reactive样式代码更好。

下一个示例描述密码和用户名验证。 工作流程如下:如果用户名无效,则无法插入密码。 假设用户名正确,但密码输入错误,则不允许我们按下按钮。 只要用户名和密码有效,带有警告文本的标签就会消失。

  导入UIKit 

  让minimumUsernameLength = 5   
  让minimalPasswordLength = 5 

  类RealSimpleValidationViewController:ViewController {   
      @IBOutlet弱var usernameOutlet:UITextField!  { 
          didSet { 
              self.usernameOutlet.addTarget(self,action:#selector(RealSimpleValidationViewController.usernameChanged(_ :)),forControlEvents:UIControlEvents.EditingChanged) 
          } 
      } 
      @IBOutlet弱var usernameValidOutlet:UILabel! 

      @IBOutlet弱var passwordOutlet:UITextField!  { 
          didSet { 
              self.passwordOutlet.addTarget(self,action:#selector(RealSimpleValidationViewController.passwordChanged(_ :)),forControlEvents:UIControlEvents.EditingChanged) 
              self.passwordOutlet.enabled = self.paswordIsValid() 
          } 
      } 
      @IBOutlet弱var passwordValidOutlet:UILabel! 

      @IBOutlet弱var doSomethingOutlet:UIButton! 

      func usernameChanged(textField:UITextField){ 
          self.passwordOutlet.enabled = self.usernameIsValid() 
          self.usernameValidOutlet.hidden = self.usernameIsValid() 
          self.doSomethingOutlet.enabled = self.usernameIsValid()&& self.paswordIsValid() 
      } 
      func passwordChanged(textField:UITextField){ 
          self.passwordValidOutlet.hidden = self.paswordIsValid() 
          self.doSomethingOutlet.enabled = self.usernameIsValid()&& self.paswordIsValid() 
      } 
      func paswordIsValid()-> Bool { 
          返回self.passwordOutlet.text?.characters.count> minimalPasswordLength 
      } 
      func usernameIsValid()-> Bool { 
          返回self.usernameOutlet.text?.characters.count> minimumUsernameLength 
      } 
  } 

RxSwift示例

  导入UIKit   
  #if!RX_NO_MODULE 
  导入RxSwift   
  进口RxCocoa   
  #万一 

  //让minimalUsernameLength = 5 
  //让minimalPasswordLength = 5 

  类SimpleValidationViewController:ViewController { 

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

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

      @IBOutlet弱var doSomethingOutlet:UIButton! 

      覆盖func viewDidLoad(){ 
          super.viewDidLoad() 

          usernameValidOutlet.text =“用户名必须至少为\(minimumUsernameLength)个字符” 
          passwordValidOutlet.text =“密码必须至少为\(minimalPasswordLength)个字符” 

          让usernameValid = usernameOutlet.rx_text 
              .map {$ 0.characters.count> = minimumUsernameLength} 
              .shareReplay(1)//没有此映射的每个绑定将执行一次,默认情况下rx是无状态的 

          让passwordValid = passwordOutlet.rx_text 
              .map {$ 0.characters.count> = minimalPasswordLength} 
              .shareReplay(1) 

          让everythingValid = Observable.combineLatest(usernameValid,passwordValid){$ 0 && $ 1} 
              .shareReplay(1) 

          usernameValid 
              .bindTo(passwordOutlet.rx_enabled) 
              .addDisposableTo(disposeBag) 

          usernameValid 
              .bindTo(usernameValidOutlet.rx_hidden) 
              .addDisposableTo(disposeBag) 

          passwordValid 
              .bindTo(passwordValidOutlet.rx_hidden) 
              .addDisposableTo(disposeBag) 

          一切有效 
              .bindTo(doSomethingOutlet.rx_enabled) 
              .addDisposableTo(disposeBag) 

          doSomethingOutlet.rx_tap 
              .subscribeNext {self中的[weak self] ?. showAlert()} 
              .addDisposableTo(disposeBag) 
      } 
  } 

让我们再讨论一个示例并实现这两个目标:使用一些RX运算符以一种反应性的方式改进我们的思维,可以肯定的是,用RX库编写的不同语言的代码片段实际上是相似且易于阅读的。

想象一下,您已经完成了一个漫长的开发周期,所有测试和应用抛光都已经结束了-看起来您只需要向商店提交应用即可。 反过来,它需要完成三件事:
开发人员方面的新版本
发行说明
UI / UX部门的一些资产。

准备要提交的应用程序的人应安排所有3个部分。 目前,我们拥有所需的一切。 下一步意味着将问题翻译成反应性语言。 在应用程序进入商店之前,应考虑4个可观察到的因素:
— releaseObservable —被所有人观察到的; 对应用的发布感兴趣(例如,产品所有者)。 — appBuildObservable —一旦正确准备好构建,便生成构建对象; — releaseNotesObservable —发布发行说明; — appStoreAssetsObservable —发出商店所需的资产。

appBuildObservable更加有趣。 我们必须逐字测试每个构建,因为没有QA工程师,它就无法上线。 说到反应式语言,我们需要对可观察对象进行过滤,以避免产生不适当的结果。

最终,我们有不同的观测值。 如何与他们一起比赛以获得合格的成绩? 我建议使用zip运算符来组合appBuildObservable,releaseNotesObservable和appStoreAssetsObservable。 它通过指定的函数将多个可观测对象的发射合并在一起,并为每个组合生成单个项。

创建结果可观测值之后,我们需要订阅它并开始接收数据。 我们可以确定希望观察对象使用哪个线程来执行操作(subscribeOn),以及我们希望在哪个线程上接收结果(observeOn)。

而已! 收到可观察到的将产生新数据。 前面提到的3个组件(新建,发行说明和UI / UX资产)将在后台线程中完成。 结果将被发送到应用程序的主线程。

上面提供的代码段是用Swift和Java编写的。 我认为它们非常易于阅读,看起来非常相似。 唯一的差异是由这两种编程语言之间的自然差异引起的。

此外,我们可以编写一个可以用不同语言以相同方式读取的代码。 我们的例子很好地证明了这一点。 由于反应式编程不仅是一个库,而是一种编程范例,因此有一些可用于最流行的语言和平台的实现。

响应式编程使我们首先考虑组织数据流,然后考虑如何编码。 此外,反应式库在不同语言的实现中为我们提供了相同的API和实体。 RxSwift中的Observable,Observer,Scheduler的含义与RxJava和RxAndroid中的Observable,Observer,Scheduler完全相同。 换句话说,如果我们为不同的平台开发相同的应用程序,以反应方式构建应用程序会带来很多好处。 我们可以为不同的操作系统构建相同的架构模型,并且仅使用本地方法编写应用程序的某些部分。

结论
在我看来,Rx代码显然是清晰,简单且易于管理的。 您实际上可以理解所有内容,而无需冗长的解释。

我们的结论是什么? 您应该回答以下问题。 您是否对现在使用反应性方法感兴趣,或者这不是您要找的东西?

对我而言,Reactive是构建时尚应用程序的一个新的强大方向。 我绝对推荐。