美文网首页RxSwiftiOS开发-fj
iOS 响应式编程初探 - RxSwift

iOS 响应式编程初探 - RxSwift

作者: JARVIS_STUDIO | 来源:发表于2016-04-18 11:51 被阅读2730次

    @author Valie Email Weibo or Github


    RxSwift是一个全新的第三方库,是Rx的Swift版本。
    你可以在gitHub上查看RxSwift使用文档。
    这部分主要是学习RxSwift项目中的示例项目,了解下RxSwift在iOS中的正确使用姿势。。> 如果你还不了解什么是Rx,可以去看下[Rx中文翻译文档]
    (https://mcxiaoke.gitbooks.io/rxdocs/content/operators/CombineLatest.html)
    ,懒得看的话我中间也会解释一下相关的东西。。
    gitHub上的RxSwift的Demo,你可以在使用CocoaPods在终端输入:pod try RxSwift 来run the example app,或者clone下来,然后

    • Open Rx.xcworkspace
    • Choose one of example schemes (RxExample-iOS) and hit Run.

    RxSwift的使用

    使用CocoaPods,在你的Podfile添加以下语句:
    use_frameworks!
    pod 'RxSwift', '~> 2.4'
    pod 'RxCocoa', '~> 2.4'
    pod 'RxBlocking', '~> 2.4'
    然后pod install一下,在需要使用的文件中import RxSwift。

    示例讲解

    示例1

    第一个示例是一个简单的加法计算,打开demo,Examples -> NumbersViewController.swift:

        @IBOutlet weak var number1: UITextField!
        @IBOutlet weak var number2: UITextField!
        @IBOutlet weak var number3: UITextField!
        @IBOutlet weak var result:  UILabel!
        override func viewDidLoad() {
            super.viewDidLoad()
            Observable.combineLatest(number1.rx_text, number2.rx_text, number3.rx_text) { value1, value2, value3 -> Int in
                return (Int(value1) ?? 0) + (Int(value2) ?? 0) + (Int(value3) ?? 0)
                }
                .map{ $0.description }
                .bindTo(result.rx_text)
                .addDisposableTo(disposeBag)
        }
    

    Observable

    在Rx中观察者模式中,observable是被观察的对象,一旦数据发生变化,Observable会发射数据或数据序列给它的观察者或订阅者(Observer),观察者之后作出相应的响应。在这里,我们需要对三个textField的输入进行监听,因此需要创建一个observable,在输入变化时,不断地将三个输入值的总和发射给result的rx_text。

    combineLatest

    Rx的操作符让你可以变换、组合、操纵和处理Observable发射的数据.
    combineLatest结合操作符:当原始Observables的任何一个发射了一条数据时,CombineLatest使用一个函数结合它们最近发射的数据,然后发射这个函数的返回值。即 combineLatest(Observable,Observable,Func2))

    23-16-45.jpg

    combineLatest接受2到9个Observable作为参数,或者单个Observables列表作为参数。

    rx_text

    如果想要监听文字的输入,我们需要一个observable不断地给我们发送新的输入值。rx_text是RxSwift针对Cocoa库作的一个封装,看一下它的定义:

    extension UITextField {
        /**
        Reactive wrapper for `text` property.
        */
        public var rx_text: ControlProperty<String> {
            return UIControl.rx_value(
                self,
                getter: { textField in
                    textField.text ?? ""
                }, setter: { textField, value in
                    textField.text = value
                }
            )
                }
        }
    

    ControlProperty遵循ControlPropertyType协议:

    public protocol ControlPropertyType : ObservableType, ObserverType {
        /**
        - returns: `ControlProperty` interface
        */
        func asControlProperty() -> ControlProperty<E>
    }
    

    也就是rx_text既是一个可被订阅者(ObservableType),又是一个订阅者 (ObserverType),也就是说它是一个Subject对象。这里number1,number1,number3的rx_text是均是observable,result的rx_text是一个observer。

    map变换操作符

    map操作符对原始Observable发射的每一项数据应用一个你自定义的函数,然后返回一个发射这些结果的Observable。

    23-55-37.jpg

    bindTo

    创建一个新的订阅,并且把observable发射来的数据传递给observer,这里result的rx_text是一个observer。即将新数据绑定到result的rx_text上。

    Dispose Bags

    订阅时,当使用完一个数据序列时,需要释放掉它们所分配的资源,不然很耗内存,我们可以对每个订阅调用dispose(),例如:

    let subscription = Observable<Int>.interval(0.3, scheduler: scheduler)//interval操作符创建一个按固定时间间隔发射一个无限递增的整数序列的observable
        .subscribe { event in
            print(event)
        }//使用subscribe操作符订阅这个observable
    NSThread.sleepForTimeInterval(2)
    subscription.dispose()
    

    This will print:

    0
    1
    2
    3
    4
    5
    ...
    

    但是手动调用dispose()是不推荐,有一个更好的方法去处理这些订阅,即使用DisposeBag。
    DisposeBag有点像是ARC,先把分配的资源统一丢到袋子里 (有点像是 autoreleasepool),然后当DisposeBag销毁的时候就一起销毁这些资源。

    示例2

    打开demo,Examples -> SimpleValidationViewController.swift
    示例2是一个用户名密码输入界面,用户名输入满足条件时,用户名红色提示隐藏并且密码栏变为可输入状态;密码输入满足条件时,密码红色提示隐藏并且按钮变为可点击状态,弹出提示框。
    所以需要监听的是用户名的输入usernameOutlet.rx_text和密码的输入passwordOutlet.rx_text。

            let usernameValid = usernameOutlet.rx_text
                .map { $0.characters.count >= minimalUsernameLength}
                .shareReplay(1)
    

    上面的map返回一个序列元素为一个Bool型的observable;
    在解释shareReplay(bufferSize: Int)前,我们先来看一下这些:

    //        let usernameValid = usernameOutlet.rx_text
    //            .map { $0.characters.count >= minimalUsernameLength}
    //            .shareReplay(1)
            let usernameValid = usernameOutlet.rx_text
                .map{ element -> Bool in
                        print("科科~")
                    return element.characters.count >= minimalUsernameLength
                }   
    //         .shareReplay(1)     
            usernameValid
                .bindTo(usernameValidOutlet.rx_hidden)
                .addDisposableTo(disposeBag)
            usernameValid
                .bindTo(passwordOutlet.rx_enabled)
                .addDisposableTo(disposeBag)
    
    

    点击进入Simple validation界面时,输出:

    科科~
    科科~
    
    

    添加.shareReplay(1)之后,重新点击进入,输出:

    科科~
    

    可以看出输出少了一次,why?
    usernameValid在上面有两个观察者:usernameValidOutlet.rx_hidden和passwordOutlet.rx_enabled。第一个观察者订阅usernameValid时,调用map里的print函数,第二个观察者在订阅时(没有添加.shareReplay(1))时,又再次调用map里的print函数,以此类推,如果有很多观察者的话就要调用很多次,而从第二个观察者开始需要的只是map返回的一个序列,而不是让其徒劳地调用map里的函数,那么怎样解决在多个观察者订阅时多次重复调用执行的问题?
    恩对,使用shareReplay(bufferSize: Int)就ok了。
    shareReplay会返回一个新的事件序列,它监听底层序列(这里指的是map返回的序列)的事件,并且通知自己的订阅者们。不过和传统的订阅不同的是,它是通过『重播』的方式通知自己的订阅者,因此在这里通过shareReplay订阅的map并不会调用多次。
    参数bufferSize指的是重播的最大元素个数,因为usernameValid是一个只有一个元素的序列observable,因此shareReplay参数为1;假如对于一个有5个元素的序列,你只需要重复播报最后3个,那么就写成.shareReplay(3),就酱紫。

    相关文章

      网友评论

        本文标题:iOS 响应式编程初探 - RxSwift

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