美文网首页
RxSwift入坑笔记

RxSwift入坑笔记

作者: iOSUI拖拽工程师 | 来源:发表于2017-10-12 19:45 被阅读329次

自学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)

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

相关文章

  • RXSwift学习资料

    使用自带教程入门 RxSwift 入坑手册 Part0 - 基础概念 RxSwift 入坑手册 Part1 - 示...

  • RxSwift入坑笔记

    自学Swift有一段时间了,在一个技术群里偶然听到RxSwift的概念,了解了以后,觉得很有必要学一学。但是开始接...

  • RXSwift 入坑记

    以前一直都是命令式编程,这个view干嘛,那个label干嘛,这样的代码写了几个世纪了,实在是疲劳了而且重复使用率...

  • RxSwift 网络请求

    一、说明 入坑RxSwift 有段时间了,之前在项目中只是小范围的使用RxSwift,为了更好的使用响应式编程,决...

  • RxSwift项目实践

    RxSwift-MVVM 这个项目是入坑RxSwift以来的一些收获,历经多个真实项目的实践。我也一直在为写出简洁...

  • Rxswift入坑系列(2)

    rx 不建议开始就硬要把基础的啃透了在入手 只需要看的懂做了什么,做完有什么效果可以先入手一些实战了. 模仿官方d...

  • Rxswift入坑系列(1)

    必须的swift需要会 推荐大家一个学习swift的最好的网站https://www.raywenderlich....

  • Rxswift入坑系列(3)

    rxswift近期使用总结 先说坑吧 1 2 方式一和二是完全不相同的方式一是正常的当task.type 产生时就...

  • RxSwift学习

    参考 RxSwift中文文档 RxSwift-Tutorial RxSwift笔记 - RxCocoa 基础 (一...

  • RXSwift的使用用法

    发现 关注 消息 RxSwift入坑解读-你所需要知道的各种概念 沸沸腾关注 2016.11.27 19:11*字...

网友评论

      本文标题:RxSwift入坑笔记

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