iOS 响应式编程初探 - RxSwift


@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),就酱紫。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,108评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,699评论 1 296
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,812评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,236评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,583评论 3 288
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,739评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,957评论 2 315
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,704评论 0 204
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,447评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,643评论 2 249
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,133评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,486评论 3 256
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,151评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,108评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,889评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,782评论 2 277
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,681评论 2 272

推荐阅读更多精彩内容

  • 响应式编程简介 响应式编程是一种基于异步数据流概念的编程模式。数据流就像一条河:它可以被观测,被过滤,被操作,或者...
    长夜西风阅读 3,002评论 0 5
  • //PublishSubject -> 会发送订阅者从订阅之后的事件序列 PublishSubjectlet se...
    zidon阅读 1,505评论 0 3
  • 最近在学习RxSwift相关的内容,在这里记录一些基本的知识点,以便今后查阅。 Observable 在RxSwi...
    L_Zephyr阅读 1,701评论 1 4
  • 本篇文章介主要绍RxJava中操作符是以函数作为基本单位,与响应式编程作为结合使用的,对什么是操作、操作符都有哪些...
    嘎啦果安卓兽阅读 2,787评论 0 10
  • 窗外 飞花漫天 思绪 飞回那年 犹记 那个黄昏 少年 马踏飞燕 身姿 矫健如飞 嘴角 轻轻勾起 就这样 映入 她的...
    浅忆兮阅读 140评论 0 0