美文网首页
Hello world in RxSwift

Hello world in RxSwift

作者: 醉看红尘这场梦 | 来源:发表于2020-09-29 15:44 被阅读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中的字符串,不是每一次用户输入的单个字符)。


    如何定义发生的事件

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

    • 成功:对于我们的例子来说,就是用户输入了一个字符,于是让UITextField有了新的值;
    • 失败:在我们的这个例子里,还不会有失败的情况,但是在后面我们处理网络请求事,就会处理这种事件;
    • 完成:表示“数组”中再也不会添加新的事件,例如,我们切换到了另外一个View;

    为了封装这个事件,我们很自然会想到使用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

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

    • 大部分人的直觉都会是直接在subscribeNext的closure参数中判断一下最后一个字符是否是偶数不就好了么?如果是这样,我们就又不知不觉回到了传统的基于状态的编程方式;
    • 在reactive programming里,故事是这样的:每一次输入事件发生时,UITextField的值,就被放在了时间轴上,我们要做的事情,不是处理每一个事件,然后然后根据事件值的不同做不同的处理。而是,我们可以对发生的事件进行“二次加工”,形成新的“事件数组”,并在新的事件数组中进行订阅。

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


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

    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

    相关文章

      网友评论

          本文标题:Hello world in RxSwift

          本文链接:https://www.haomeiwen.com/subject/zspyjhtx.html