RxSwift入坑笔记

自学Swift有一段时间了,在一个技术群里偶然听到RxSwift的概念,了解了以后,觉得很有必要学一学。但是开始接触真的比较难理解。从网上找了一些资料以后开始了RxSwift之旅。
主要资料参考如下(感谢分享的力量):
http://www.codertian.com/2016/12/10/RxSwift-shi-zhan-jie-du-base-demo/
http://www.codertian.com/2016/11/27/RxSwift-ru-keng-ji-read-document/
博主写的很好,所以没必要再照着写一遍。这边文章主要针对上面的博客写一些自己的理解以及总结吧。

RxSwift实战这篇文章中,是基于MVVM写的。其中用到的文件有Service,ViewModel,Protocol,ViewController(页面比较简单,并未涉及到View。)

Service主要负责一些网络请求和一些数据的访问操作,供ViewModel使用。

注册页面三个文本框加一个按钮,要实现的效果如下。
用户名和密码都有一定格式要求。


注册页面.png

先把整体的流程贴在这里。
1.将用户名的TextField流做为Observable(被观察者)绑定到ViewModel的userName。
其中userName声明为

 let username = Variable<String>("")    //初始值为""

Variable是BehaviorSubject一个包装箱,就像是一个箱子一样,使用的时候需要调用asObservable()拆箱,里面的value是一个BehaviorSubject,他不会发出error事件,但是会自动发出completed事件。

Variable和BehaviorSubject都是Subjects概念的范畴,具体可参考http://www.codertian.com/2016/11/27/RxSwift-ru-keng-ji-read-document/。此处理解为把userName声明为一个Observable即可。

2.将TextFeld的text绑定到userName上

usernameTextField.rx.text.orEmpty
        .bindTo(viewModel.username)
        .addDisposableTo(disposeBag)
// usernameTextField.rx.text.orEmpty是RxCocoa库中概念,把TextFiled的text变成了一个Observable,orEmpty把String?过滤nil
// 变为String类型。
  1. 在vewModel中处理userName
 let usernameUsable: Observable<Result>   // 这个是对userName处理以后的输出output
// 如果只是检测格式,那么可以写成如下形式

usernameUsable = userName.asObservable().map.({(userName) -> Result  in  
    if userName.characters.count == 0 {
      return .empty
    }
    if userName.characters.count < characterCount {
      return failed(message:"长度至少6个字符")
    }
    return just(.ok(message:"用户名可用")
}).shareReplay(1)
// map函数是通过传入一个函数闭包把原来的sequence转变为一个新的sequence的操作

如果这其中涉及到了网络请求,或者是耗时的读取数据库操作,那么我们会用到flatMap函数
flatMap将一个sequence转换为一个sequences,当你接收一个sequence的事件,你还想接收其他sequence发出的事件的话可以使用flatMap,她会将每一个sequence事件进行处理以后,然后再以一个新的sequence形式发出事件。

具体参考:
http://www.codertian.com/2016/12/01/RxSwift-ru-keng-ji-learn-the-difficulty/
如果我们要检测用户名是否已经存在,需要去发送网络请求或者去查询本地数据库,那么就要用flatMap

usernameUsable = userName.asObservable().flatMap({(userName) in 
  return service.validateUsername(username)
                .observeOn(MainScheduler.instance)
                .catchErrorJustReturn(.failed(message: "username检测出错")
 })

要注意的是
Map 中的闭包返回的是一个经过闭包参数userName(这也是原始序列的元素)处理过后的Result类型的元素。
flatMap中的闭包返回的是一个 Observable<Result> 序列,例如
service中validateUsername中返回的 .just(.failed(message: "号码长度至少6个字符"))

service中validateUsername方法如下

func validateUsername(_ username: String) -> Observable<Result> {
        
        if username.characters.count == 0 {//当字符等于0的时候什么都不做
            return .just(.empty)
        }
        
        if username.characters.count < minCharactersCount {//当字符小于6的时候返回failed
            return .just(.failed(message: "号码长度至少6个字符"))
        }
        
        if usernameValid(username) {//检测本地数据库中是否已经存在这个名字
            return .just(.failed(message: "账户已存在"))
        }
        
        return .just(.ok(message: "用户名可用"))
    }

观察map和flatMap函数定义

public func map<R>(_ transform: @escaping (Self.E) throws -> R) -> RxSwift.Observable<R>

public func flatMap<O : ObservableConvertibleType>(_ selector: @escaping (Self.E) throws -> O) -> RxSwift.Observable<O.E>

其实这里的map和flatMap的作用是一样的。map函数可以对原有序列里面的事件元素进行改造,返回的还是原来的序列。而flatMap对原有序列中的元素进行改造和处理,每一个元素返回一个新的sequence,然后把每一个元素对应的sequence合并为一个新的sequence序列。

观察fatMap,声明了一个遵循ObservableConvertibleType协议的泛型类型O(可以理解为一个序列)。闭包中的参数是原始序列的元素(在这指的是userName:String),闭包返回的是遵循ObservableConvertibleType协议的泛型类型O,也就是说返回的是一个序列。最后函数返回的是O类型的元素组成的一个序列:Observable<O.E>,在这里,O.E指的是Result类型,也就是说flatMap函数最后返回的是O类型的。
(有理解的不对的地方,欢迎指正)。

viewModel和Service中工作完成以后,可以在controller写后续工作了

思考?如何让userNameUsable绑定到提示用户名是否可用的Label呢?
当然可以用subscribe(onNext:)方法

    viewModel.userNameUsable.subscribe(onNext: { [weak self] (result) in
      switch result {
      case .ok(let message):
        self!.nameTipLabel.text = message
      case .empty:
        self!.nameTipLabel.text = ""
      case .failed(let message):
        self!.nameTipLabel.text = message
      }
    }).addDisposableTo(disposeBag)

但是这样写有一些繁琐,毕竟这些放在viewController里面写不太优雅,如果要在Result不同情况下显示不同颜色,那么代码量又会进一步增多。

想要解决这个,就需要用到UIBindingObserver了,这个是个很有用的东西。
我们现在为UILabel自定义一个validationResult

extension Reactive where Base: UILabel {
    var validationResult: UIBindingObserver<Base, Result> {
        return UIBindingObserver(UIElement: base) { label, result in
            label.textColor =  根据result处理
            label.text = 根据result处理
        }
    }
}

// 自定义了一个Observer,对UIlabel进行了扩展,根据result结果,进行他的text和textColor的显示

详细内容见http://www.codertian.com/2016/12/01/RxSwift-ru-keng-ji-learn-the-difficulty/

然后viewController中可以写成如下形式,优雅了很多吧!

viewModel.userNameUsable
       .bind(to: nameTipLabel.rx.validationResult)
       .addDisposableTo(disposeBag)

RxSwift中除了Observable,还有一个概念是Driver

下面来看一下Driver
其实Driver和Observable的使用结构是一样的只是Driver和Observable有点区别,Driver是RxSwift专门针对UI操作,而Observable是一个通用的东西

下面是登录页面的Driver使用
页面效果如下

登录页面.png

同样先写Service,这里如果要校验一下userName是否是已经存在的话,那么要用flatMap,因为要有耗时操作,如果只是校验一下格式的话,那么要用map。

假设这里需要校验用户名是否已存在。那么
在viewModel中声明输入如下:

// input
let userName = Observable<String>("")
// output
let userNameUsable: Driver<Result>

ViewModel的init方法中初始化userNameUsable

    userNameUsable = userName.asObservable()
                             .asDriver(onErrorJustReturn: "")
                             .flatMapLatest { username in
      return service.loginUserNameValid(username)
             .asDriver(onErrorJustReturn:
             .failed(message: "连接server失败"))
    }

在ViewController里面进行绑定

    viewModel.userNameUsable.drive(nameTipLabel.rx.validationResult)
                            .addDisposableTo(disposeBag)

如果没有自定义Observer,那么写法如下:

    viewModel.userNameUsable.drive(onNext: { [weak self] (result) in
      switch result {
      case let .ok(message):
        // 处理
      case .empty:
        // 处理
      case let .failed(message):
        // 处理
    }).addDisposableTo(disposeBag)

注意:只有Driver可以使用drive方法,Observable是不能使用的。

另外官方的写法是这样的:
viewModel初始化方法如下

init(input: (userName: Driver<String>, password: Driver<String>, loginTaps: Driver<Void>), service: ValidateService)
// init的参数使用一个元组

viewController里面

let viewModel = LoginViewModel(input: (userName: nameTextField.rx.text.orEmpty.asDriver(), password: passWordTextField.rx.text.orEmpty.asDriver(), loginTaps:doSomething.rx.tap.asDriver() ), service: ValidateService.instance)
    
viewModel.userNameUsable.drive(nameTipLabel.rx.validationResult)
                            .addDisposableTo(disposeBag)

还有剩下的列表页,使用流程和上述方式并无二致。可参考博主原文。
总之还是要多写。否则看过几天之后便全忘记了。
最后感谢原文博主分享。

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

推荐阅读更多精彩内容

  • 最近在学习RxSwift相关的内容,在这里记录一些基本的知识点,以便今后查阅。 Observable 在RxSwi...
    L_Zephyr阅读 1,750评论 1 4
  • 前言 在之前用Objective-C语言做项目的时候,我习惯性的会利用MVVM模式去架构项目,在框架Reactiv...
    Tangentw阅读 21,183评论 32 123
  • 上一篇 2016海南之行(四)物产与水果 忽然之间多肉闯入我们的生活,俘获了多少人的心,我是个后知后觉的人了,关...
    自然旅行随笔阅读 897评论 12 3
  • 时间怕是个职业马拉松选手,以前懵懂现在才慢慢发现它跑起来是那么有毅力且又是那么快。 一天天,不知不觉已经在这个北方...
    呆子先生阅读 385评论 5 3