Hello world in RxSwift

2020-09-29  本文已影响0人  醉看红尘这场梦

理解了Reactive Programming的编程思想之后,在这段里,我们使用RxSwift来实现上个视频中“筛选用户输入偶数”的例子,以此来进一步了解Reactive Programming中的各种思想的具体实现。

安装RxSwift

首先,我们使用CocoaPods在之前的项目中安装RxSwift

在终端里,进入项目目录,执行:

pod init

创建一个Podfile,然后,给它添加下面的内容:

# Uncomment this line to define a global platform for your project
platform :ios, '8.0'
# Uncomment this line if you're using Swift
use_frameworks!

target 'ThinkingInRxSwift' do
   pod 'RxSwift',    '~> 2.0'
   pod 'RxCocoa',    '~> 2.0'
end

执行pod install完成RxSwift安装。成功之后,CocoaPods会给我们提示:使用.xcworkspace扩展名的文件打开项目:

image

依旧是过滤用户输入的偶数

在Finder里打开CocoaPods生成的.xcworkspace文件。

在Main.storyboard里,我们新添加一个UITextField,并且,用同样的方式让Xcode生成约束:

image

然后,按Option + Command + Enter,打开Assistant View,按住Ctrl把UITextField拖拽到Assistant View里,在弹出的窗口里给控件设置个属性,例如:rxUserInput:

image

接下来,我们修改下之前实现的UITextFieldDelegate,让它只针对userInput对象生效:

func textField(textField: UITextField,
    shouldChangeCharactersInRange range: NSRange,
    replacementString string: String) -> Bool {

    if textField == self.userInput {
        // 1\. Map user input string to Int
        if let n = Int(string) {
            // 2\. Filter even numbers
            if n % 2 == 0 {
                print(n)
            }
        }
    }

    return true
}

这样,一切准备工作就就绪了。接下来我们就通过RxSwift来实现筛选输入偶数的功能。尽管你还没有开始,但是,相信我,它一定比你想象中简单得多。


如何定义一个”以时间为维度的数组”

RxSwiftUITextField添加了一个新的属性:rx_text,我们暂时忽略掉它的具体类型,把它理解为就是那个“以时间为维度的数组”就可以了。

而这个数组中事件的值,就是每一次用户输入之后,当前UITtextField中的字符串(注意:这里指的是输入之后,UITextField中的字符串,不是每一次用户输入的单个字符)。


如何定义发生的事件

对于每一个添加到“数组”中的事件,都存在三种可能:

为了封装这个事件,我们很自然会想到使用Swift中的enum来表示它,但是对于不同类型的成功事件,它包含的值有可能是不相同的,因此,我们使用一个泛型的enum来表达一个发生的事件:

enum Event<Element>  {
    case Next(Element)      // next element of a sequence
    case Error(ErrorType)   // sequence failed with error
    case Completed          // sequence terminated successfully
}

为什么要使用Next表示事件成功时的值呢?强行记住它也没什么问题。

这里分享一个理解方式:

因为对于“事件数组”来说,每一次添加进来的值,相对于事件发生之前来说,都是它的”下一次”事件。因此,把“成功的下一次事件”,定义为Next。


如何订阅事件的发生

此时,我们已经有了“事件数组”、有了事件对象。接下来,我们该订阅事件了。在viewDidLoad方法里,添加下面的代码:

self.rxUserInput.rx_text.subscribeNext { print($0) }

上面这行代码很好理解,我们使用subscribeNext方法订阅了rx_text“数组”中的成功事件。

subscribeNext接受一个Closure参数,这个Closure针对UITextField来说,接受一个String参数,表示当前事件发生后,UITextField中字符串的值,返回Void。在它的实现里,我们只是把这个值打印在了控制台上,方便我们观察。

完成之后,Command + R编译执行,在第二个输入框里输入“1A2B”,我们就能在控制台看到:每一次输入,控制上都打印出了当前UITextField的值:

image

这说明,我们的订阅行为已经成功了,接下来,我们要筛选中其中的偶数。怎么做呢?我们有两种选择:

这听起来有点儿抽象,我们来看代码。


如何对发生的事件进行”二次加工”

viewDidLoad里,修改我们订阅事件的代码。首先,我们把值是字符串的“事件数组”变成一个值是Int的“事件数组”:

self.rxUserInput.rx_text
    .map { (input: String) -> Int in
        if let lastchar = input.characters.last {
            if let n = Int(String(lastchar)) {
                return n
            }
        }

        return -1
    }

这里,我们使用的map方法和函数式编程中我们对数组使用的map方法作用是类似的。它接受一个closure参数,这个closure自身的参数则是原事件数组中值的类型:String,并且,我们让它返回一个Int。

这样,经过map映射之后:

  1. 我们先判断UITextField中是否有值;
  2. 把最后一次用户输入的字符转换成整数;
  3. 转换成功后,我们就返回它;

上面任何一个条件失败,我们就返回-1(注:实际上任何一个不能被2整除的数都可以)。这样,我们就把新的“事件数组”中的值变成了Int。

接下来,我们要对得到的新“事件数组”再做一次转换,筛选出所有的偶数。在map后面,添加下面的代码:

self.rxUserInput.rx_text
    .map { (input: String) -> Int in
        if let lastchar = input.characters.last {
            if let n = Int(String(lastchar)) {
                return n
            }
        }

        return -1
    }
    .filter {
        $0 % 2 == 0
    }

filter用于逐个过滤“事件数组”中的元素,它也有一个clousre参数。而这个closure的参数是“原数组”中元素的类型,在我们的例子里是Int,返回一个Bool值,表示是否满足过滤条件。

由于我们要筛选偶数,因此我们使用$0 % 2 == 0。这样,我们就得到了只包含偶数值的“事件数组”。接下来,我们直接使用之前的subscribeNext订阅就可以了。

self.rxUserInput.rx_text
    .map { (input: String) -> Int in
        if let lastchar = input.characters.last {
            if let n = Int(String(lastchar)) {
                return n
            }
        }

        return -1
    }
    .filter {
        $0 % 2 == 0
    }
    .subscribeNext {
        print($0)
    }

完成之后,Command + R编译执行,我们再输入1A2B,就可以在控制台看到,只有偶数被输出到控制台了:

image

这样,我们就使用RxSwift完成了从用户输入中过滤偶数的例子。和我们之前通过Delegate实现最根本的一个差别就是,我们从事件处理函数中设置诸多状态判断,变成了我们根据自己的需要去生成对应的事件队列。接下来,我们再回顾一下整个过程。


小结

对于用户输入的“1A2B”来说,我们的“事件数组”是这样的:

image

然后,我们先用map把数组中的每一个元素的值从String变成Int,把无法转换成Int的值变成-1:

image

然后,再使用filter过滤出所有的偶数:

image

这样,我们的“事件数组”就变成了只有当用户输入偶数时,才添加元素,最后,我们使用subscribeNext订阅其中的事件:

image
上一篇 下一篇

猜你喜欢

热点阅读