swift开发Swift

RxSwift 使用

2021-01-13  本文已影响0人  时光啊混蛋_97boy

原创:知识点总结性文章
创作不易,请珍惜,之后会持续更新,不断完善
个人比较喜欢做笔记和写总结,毕竟好记性不如烂笔头哈哈,这些文章记录了我的IOS成长历程,希望能与大家一起进步
温馨提示:由于简书不支持目录跳转,大家可通过command + F 输入目录标题后迅速寻找到你所需要的内容

目录


一、函数响应式编程

1、函数式

a、定义

函数式编程简称FP(Functional Programming),函数式编程就是一种抽象程度很高的编程范式,它将计算机运算看做是数学中函数的计算,而纯粹的函数式编程语言编写的函数没有变量,因此,任意一个函数,只要输入是确定的,输出就是确定的,这种纯函数我们称之为没有副作用。而允许使用变量的程序设计语言,由于函数内部的变量状态不确定,同样的输入,可能得到不同的输出,因此,这种函数是有副作用的。

b、特点

允许把函数本身作为参数传入另一个函数,同时还允许返回一个函数。

// f是函数 y是返回值 x是参数 
// x参数自身又可以作为其他函数的y值,造成嵌套
// 假如x参数为2,2 = 1+1 = 0+2 可以存在多种变式
函数表达式: y = f(x) ---> x = f(x) ---> y = f(f(x))
c、范例
需求

对于数组 [1,2,3,4,5,6,7,8,9,10],首先获取 > 3 的数字,获取到数字之后 + 1,再输出所有数字中的偶数。

普通写法

代码嵌套层次非常深,可读性和复用性也是非常差的,维护以及代码构建成本太高,这样就成了函数式出现的必要性。

let array = [1,2,3,4,5,6,7]
for num in array
{
    if num > 3
    {
        let number = num + 1
        if (number % 2 == 0)
        {
            print(number)// 输出结果为6和8,是5和7增加1后的数字
        }
    }
}
函数式写法
array.filter{ $0 > 3 }
    .filter{ ($0 + 1) % 2 == 0 }
    .forEach{ print($0) }// 输出结果为5和7,即原来的数组中的数字本身

2、响应式

a、定义

对象对某一数据流变化做出响应的这种编码方式称为响应式。例如我们在爱奇艺平台观看视频,我们只需要在某一个时刻订阅了这个视频,后面平台自媒体运营者不断更新视频,我们随着时间也能自定接受推送,这就是响应。再比如小明今年18岁,小凡15岁,小明比小凡大3岁,10年后小明28岁,小凡的年龄也要随着小明的年龄变化而变为25岁,而不是仍然停留在15岁。

在iOS开发中我们经常会响应一些事件buttontaptextFieldtextViewnotifactionKVONSTimer等等这些,都需要做响应监听,响应后都需要在对应的响应事件中去做处理。在原生开发中,触发对象与响应方法是分离的,如button的初始化和点击响应方法是分离的,这样导致功能代码与逻辑代码分析需要分开进行容易出错。

b、使用KVO实现响应式范例
输出结果
敌军发送了一条至关重要的情报
报告长官,我部已成功截获敌军信息
Optional([__C.NSKeyValueChangeKey(_rawValue: new): XieJiapei 平民,这个人真实身份是卧底,速速接应, __C.NSKeyValueChangeKey(_rawValue: kind): 1])
❶ 人物身份
class Person: NSObject
{
   @objc dynamic var identity:String = "XieJiapei 平民"
}
❷ 手指点击屏幕更改了人物身份
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?)
{
    print("敌军发送了一条至关重要的情报")
    person.identity = "\(person.identity),这个人真实身份是卧底,速速接应"
}
❸ 使用KVO实现响应式,为人物身份添加观察者观察该值变化
func useKVO()
{
    self.person.addObserver(self, forKeyPath: "identity", options: .new, context: nil)
}
❹ KVO中观察到值发生变化时调用的函数
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?)
{
    print("报告长官,我部已成功截获敌军信息")
    print(change as Any)
}
❺ 销毁KVO中的观察者,不再进行观察该值变化
deinit
{
    self.removeObserver(self.person, forKeyPath: "identity")
}

3、RxSwift

a、定义

RxSwiftReactiveX 家族的重要一员,ReactiveXReactive Extensions 的缩写,一般简写为 RxReactiveX 官方给Rx的定义是:Rx是一个使用可观察数据流进行异步编程的编程接口。ReactiveX 不仅仅是一个编程接口,它是一种编程思想的突破,它影响了许多其它的程序库和框架以及编程语言。它拓展了观察者模式,使你能够自由组合多个异步事件,而不需要去关心线程,同步,线程安全,并发数据以及I/O阻塞。

RxSwiftRx 为 Swift 语言开发的一门函数响应式编程语言, 它可以代替iOS系统的 Target Action / 代理 / 闭包 / 通知 / KVO,同时还提供网络、数据绑定、UI事件处理、UI的展示和更新、多线程等。

RxSwift最典型的特色就是解决Swift这门静态语言的响应能力,利用随时间维度序列变化为轴线,用户订阅关心能随轴线一直保活,达到订阅一次,响应一直持续。

RxSwift需要导入两个框架。RxSwift框架提供了RX的核心逻辑,是函数响应式编程的思想所在。RxCocoa框架使UIKit框架下的各种控件具有RX提供的各种功能。

import UIKit
import RxCocoa
import RxSwift
b、使用RxSwift实现响应式范例
self.person.rx.observeWeakly(String.self, "identity") 
            .subscribe(onNext: { (value) in print(value as Any)})
            .disposed(by: disposeBag)

输出结果为

Optional("XieJiapei 平民")
敌军发送了一条至关重要的情报
Optional("XieJiapei 平民,这个人真实身份是卧底,速速接应")

二、RxSwift的UI用法

1、button点击事件

a、传统的添加UI事件的方式

缺点是UI控件的初始化与触发的事件不在同一个位置。

self.button.addTarget(self, action: #selector(didClickButton), for: .touchUpInside)
b、使用RxSwift来控制button响应
button.backgroundColor = .black
button.setTitle("Button", for: .normal)
self.button.rx.tap
    .subscribe(onNext: { () in print("点击了按钮")})
    .disposed(by: disposeBag)
self.view.addSubview(button)

倘若要控制响应事件的触发方式,可以使用以下方式

self.button.rx.controlEvent(.touchUpOutside)

2、textfiled文本响应

a、传统的textfiled文本响应的方式
self.textFiled.delegate = self

extension ReplacesUIElement: UITextFieldDelegate
{
    func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool
    {
        if let num = Int(string)
        {
            if num % 2 == 1
            {
                print("打印输入的奇数:\(num)")// 输出1、3、5
            }
        }
        return true
    }
}
b、使用RxSwift来控制textfiled文本响应
self.textFiled.rx.text.orEmpty.changed
    .subscribe(onNext: { (text) in print("监听到用户输入的文本为:\(text)") })
    .disposed(by: disposeBag)
输出的结果为
监听到用户输入的文本为:
监听到用户输入的文本为:1
监听到用户输入的文本为:12
监听到用户输入的文本为:123
监听到用户输入的文本为:1234
监听到用户输入的文本为:12345
监听到用户输入的文本为:123456
将textfiled文本值传递到button作为title
self.textFiled.rx.text
    .bind(to: self.button.rx.title())
    .disposed(by: disposeBag)

3、使用RxSwift来控制scrollView滚动效果

scrollView.backgroundColor = .yellow
scrollView.contentSize = CGSize(width: 200, height: 300 * 4)
self.view.addSubview(scrollView)

scrollView.rx.contentOffset
    .subscribe(onNext: { [weak self] (content) in
        self?.view.backgroundColor = UIColor.init(red: content.y/255.0*0.8, green: content.y/255.0*0.3, blue: content.y/255.0*0.6, alpha: 1.0)
        print(content.y)
    })
    .disposed(by: disposeBag)

输出结果为:

643.3333333333334
644.6666666666666
648.6666666666666
690.0
......

4、使用RxSwift来控制手势响应

let tap = UITapGestureRecognizer()
self.label.addGestureRecognizer(tap)
self.label.isUserInteractionEnabled = true
self.label.text = "手势"
self.label.backgroundColor = .brown
view.addSubview(self.label)

tap.rx.event
    .subscribe(onNext: { tap in print(tap.view!)})
    .disposed(by: disposeBag)

输出结果为:

<UILabel: 0x7fa54190e8d0; frame = (130 620; 200 50); text = '手势'; gestureRecognizers = <NSArray: 0x600003779f50>; layer = <_UILabelLayer: 0x600001a3c780>>

5、使用RxSwift来控制通知事件

NotificationCenter.default.rx.notification(UIResponder.keyboardWillShowNotification)
            .subscribe(onNext: { notification in print(notification)})
            .disposed(by: disposeBag)

输出结果为:

name = UIKeyboardWillShowNotification, object = nil, userInfo = ......

6、使用RxSwift来控制timer定时器

a、传统控制定时器的方式
方案一

NSTimer是会受runloop影响的,它的准确性依赖于runloop的状态。

self.traditionTimer = Timer.init(timeInterval: 1, target: self, selector: #selector(timerFire), userInfo: nil, repeats: true)
// 只会打印一次
self.traditionTimer.fire()

// 会打印多次,但是和滚动视图冲突,在滚动时计时器停止
RunLoop.current.add(self.traditionTimer, forMode: .default)

// 会打印多次,在滚动时计时器继续运行
RunLoop.current.add(self.traditionTimer, forMode: .common)
方案二
// 会打印多次,但是和滚动视图冲突,在滚动时计时器停止
self.traditionTimer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true, block: { (timer) in
    print("喝点小酒")
})
方案三

GCD封装的timer的准确性是比较高的,经常是追求性能的小伙伴们封装工具类时的一个不错选择。

// 会打印多次,在滚动时计时器继续运行
gcdTimer = DispatchSource.makeTimerSource()
gcdTimer.schedule(deadline: DispatchTime.now(), repeating: DispatchTimeInterval.seconds(1))
gcdTimer.setEventHandler {
    print("脑袋晕晕的")
}
gcdTimer.resume()// 开始
gcdTimer.suspend()// 暂停
gcdTimer.cancel()// 取消
gcdTimer = nil// 销毁
方案四
// 会打印多次,但是和滚动视图冲突,在滚动时计时器停止
displayLink = CADisplayLink(target: self, selector: #selector(timerFire))
displayLink.preferredFramesPerSecond = 1
displayLink.add(to: .current, forMode: .default)
displayLink.isPaused = true // 暂停
b、使用RxSwift来控制timer定时器
timer = Observable<Int>.interval(RxTimeInterval.seconds(1), scheduler: MainScheduler.instance)// 主线程
timer.subscribe(onNext: { num in print("下雪了❄️")})
    .disposed(by: disposeBag)

可以看到RXSwift的计时器使用的不是NSTimer,因为当我们拖动滚动视图的时候,计时器并没有停止工作。RxSwift实现的timer免去了我们计时器的一些不必要的麻烦,包括runloop影响、销毁问题、线程问题。

给垃圾袋重新赋值,那么旧的垃圾袋就会被销毁掉,而旧的垃圾袋里面含有计时器,所以计时器也同时被销毁掉了,不再运行。其他停止计时器的方式还包括完成、错误。

override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?)
{
    self.disposeBag = DisposeBag()
}

7、使用RxSwift来控制网络请求

传统的网络请求方式
let url = URL(string: "https://www.baidu.com")
URLSession.shared.dataTask(with: url!)
{ (data, response, error) in
    print(String.init(data: data!, encoding: .utf8)!)
}.resume()

输出结果为:

href=http://home.baidu.com>关于百度</a> <a href=http://ir.baidu.com>About Baidu</a> </p> <p id=cp>&copy;2017&nbsp;Baidu&nbsp;<a href=http://www.baidu.com/duty/>使用百度前必读</a>&nbsp; <a href=http://jianyi.baidu.com/ class=cp-feedback>意见反馈</a>&nbsp;京ICP证030173号&nbsp; <img
使用RxSwift来控制网络请求
let url = URL(string: "https://www.baidu.com")
URLSession.shared.rx.response(request: URLRequest(url: url!))
    .subscribe(onNext: { (response, data) in print(data)})
    .disposed(by: disposeBag)

输出结果为:

curl -X GET 
"https://www.baidu.com" -I -v
Success (205ms): Status 200
2443 bytes

8、使用RxSwift来控制按钮是否可以点击

a、需求

当用户输入用户名时,如果用户名不足 5个字就给出红色提示语,并且无法输入密码,当用户名符合要求时才可以输入密码。同样当用户输入的密码不到 5 个字时也给出红色提示语。

当用户名和密码有一个不符合要求时底部的绿色按钮不可点击,只有当用户名和密码同时有效时按钮才可点击。当点击绿色按钮后弹出一个提示框。

b、使用RxSwift进行实现

用户名是否有效

let usernameVaild = usernameTextFiled.rx.text.orEmpty
            .map { (text) -> Bool in return text.count >= minUsernameLength }

根据用户名是否有效来判断用户名有效提示框是否需要隐藏,密码输入框是否允许输入

usernameVaild.bind(to: usernameValidLabel.rx.isHidden)
    .disposed(by: disposeBag)
usernameVaild.bind(to: passwordTextFiled.rx.isEnabled)
    .disposed(by: disposeBag)

密码是否有效

let passwordVaild = passwordTextFiled.rx.text.orEmpty
            .map { (text) -> Bool in return text.count >= minPasswordLength }

根据密码是否有效来判断密码有效提示框是否需要隐藏

passwordVaild.bind(to: passwordValidLabel.rx.isHidden)
            .disposed(by: disposeBag)

密码和用户名同时有效才允许按钮点击

 Observable.combineLatest(usernameVaild, passwordVaild){ $0 && $1}
            .bind(to: loginBtn.rx.isEnabled)
            .disposed(by: disposeBag)

点击按钮打印文本

loginBtn.rx.tap.subscribe(onNext: { () in
    print("源代码分析好恶心,头都绕晕了")
}).disposed(by: disposeBag)

9、使用RxSwift来控制Table视图

这是一个过分使用了RxSwift来完成功能的典型例子,本来一定程度上可以简便我们工作量的RxSwift,一旦过度使用,代码逻辑就会变得混乱不堪,不懂这个框架的高级内容的人根本无法看懂你在表达什么,只会觉得一头雾水,想揍扁你的冲动都有了,反正我是这样觉得的。这东西好处就是简便,坏处就是非常绕,无论是使用方式还是源码分析。

a、正在运行
let isRunning = Observable
    .merge([playButton.rx.tap.map({ return true }), stopButton.rx.tap.map({ return false })])
    .startWith(false)
    .share(replay: 1, scope: .whileConnected)

打印是否正在运行

isRunning
    .subscribe(onNext: { print($0) })
    .disposed(by: disposeBag)

正在运行序列和停止按钮是否能够点击进行绑定

isRunning
    .bind(to: stopButton.rx.isEnabled)
    .disposed(by: disposeBag)
b、没有运行
let isNotRunning = isRunning
    .map({ running -> Bool in
            print(running)
            return !running })
    .share(replay: 1, scope: .whileConnected)

将没有运行和运行按钮能够点击事件绑定,和割裂按钮隐藏事件绑定

isNotRunning
    .bind(to: splitUpButton.rx.isHidden)
    .disposed(by: disposeBag)
isNotRunning
    .bind(to: playButton.rx.isEnabled)
    .disposed(by: disposeBag)
c、计时器

创建计时器

timer = Observable<Int>
    .interval(DispatchTimeInterval.milliseconds(100), scheduler: MainScheduler.instance)
    .withLatestFrom(isRunning, resultSelector: {_, running in running})
    .filter({running in running})
    .scan(0, accumulator: {(acc, _) in
        return acc+1
    })
    .startWith(0)
    .share(replay: 1, scope: .whileConnected)

打印计时器中的时间

timer
    .subscribe { (milliseconds) in print("\(milliseconds)00ms") }
    .disposed(by: disposeBag)

将格式化后的日期展示到Label

timer.map(stringFromTimeInterval)
    .bind(to: timeLabel.rx.text)
    .disposed(by: disposeBag)
d、割开序列

以计时器的当前时间为准割开作为片段

let lapsSequence = timer
    .sample(splitUpButton.rx.tap)
    .map(stringFromTimeInterval)
    .scan([String](), accumulator: { lapTimes, newTime in
        return lapTimes + [newTime]
    })
    .share(replay: 1, scope: .whileConnected)

将割开的片段展示到Table

lapsSequence
    .bind(to: tableView.rx.items (cellIdentifier: "Cell", cellType: UITableViewCell.self))
    { (row, element, cell) in
        cell.textLabel?.text = "\(row+1)) \(element)"
    }
    .disposed(by: disposeBag)

设置tableviewdelegate

tableView
    .rx.setDelegate(self)
    .disposed(by: disposeBag)

更新Tableviewheadview里面的计数

lapsSequence.map({ laps -> String in
    return "\t\(laps.count) laps"
})
    .startWith("\tno laps")
    .bind(to: countLable.rx.text)
    .disposed(by: disposeBag)

三、observable可观察序列的创建

1、emty方法

let emptyObservable = Observable<Int>.empty()

_ = emptyObservable.subscribe(onNext: { (number) in
    print("订阅:",number)
}, onError: { (error) in
    print("错误:",error)
}, onCompleted: {
    print("完成")
}) {
    print("释放回调")
}

输出结果为:

完成
释放回调

本来序列事件是Int类型的,但是这里调用emty函数返回空序列,所以直接进入complete


2、just方法

我们显示地标注出了Observable的类型为Observable<[String]>,即指定了这个 Observable 所发出的事件携带的数据类型必须是String 类型的。该方法通过传入一个默认值来初始化单个信号序列,构建一个只有一个元素的Observable队列,订阅完信息自动complete

方式一
let array = ["天地玄黄","宇宙洪荒"]
Observable<[String]>.just(array)
    .subscribe { (number) in print(number)}
    .disposed(by: disposeBag)

输出结果为:

next(["天地玄黄", "宇宙洪荒"])
completed
方式二
_ = Observable<[String]>.just(array).subscribe(onNext: { (number) in
    print("订阅:",number)
}, onError: { (error) in
    print("错误:",error)
}, onCompleted: {
    print("完成回调")
}) {
    print("释放回调")
}

输出结果为:

订阅: ["天地玄黄", "宇宙洪荒"]
完成回调
释放回调

3、of方法

可以接受同类型的数量可变的参数,无论字典、数组还是多个元素都可以正常使用。

多个元素
Observable<String>.of("百尺竿头","更进一步")
    .subscribe { (event) in print(event) }
    .disposed(by: disposeBag)

输出结果为:

next(百尺竿头)
next(更进一步)
completed
字典
Observable<[String: Any]>.of(["name":"谢佳培","age":23])
    .subscribe { (event) in
        print(event)
    }.disposed(by: disposeBag)

输出结果为:

next(["age": 23, "name": "谢佳培"])
completed
数组
Observable<[String]>.of(["瑞幸咖啡","神州优车集团"])
    .subscribe { (event) in print(event)}
    .disposed(by: disposeBag)

输出结果为:

next(["瑞幸咖啡", "神州优车集团"])
completed

4、from方法

当我们从数组、集合中获取序列的时候需要对可选项进行处理,该序列会将可选序列转换为可观察序列,更加安全。

func from()
{
    Observable<[String]>.from(optional: ["华东师范大学","厦门大学","华侨大学"])
        .subscribe { (event) in print(event) }
        .disposed(by: disposeBag)
}

输出结果为:

next(["华东师范大学", "厦门大学", "华侨大学"])
completed

5、deferred方法

使用deferred()方法延迟Observable序列的初始化。通过传入的block来实现根据外界的条件返回不同的Observable序列的初始化。

let isOdd = true// 是否是奇数
_ = Observable<Int>.deferred
{ () -> Observable<Int> in
    if isOdd
    {
        return Observable.of(1,3,5,7,9)
    }
    else
    {
        return Observable.of(0,2,4,6,8)
    }
}
.subscribe { (event) in print(event) }

输出结果为:

next(1)
next(3)
next(5)
next(7)
next(9)
completed

6、rang方法

生成指定范围的可观察序列。

Observable.range(start: 2, count: 5)
    .subscribe { (event) in print(event) }
    .disposed(by: disposeBag)

输出结果为:

next(2)
next(3)
next(4)
next(5)
next(6)
completed

7、generate方法

整数累加
Observable.generate(initialState: 0,// 初始值
                    condition: { $0 < 10}, // 条件 直到小于10
                    iterate: { $0 + 2 })  // 迭代 每次+2
    .subscribe { (event) in print(event) }
    .disposed(by: disposeBag)

输出结果为:

next(0)
next(2)
next(4)
next(6)
next(8)
completed
数组遍历
let array = ["女孩1","女孩2","女孩3","女孩4","女孩5"]
Observable.generate(initialState: 0,
    condition: { $0 < array.count},
    iterate: { $0 + 1 })
    .subscribe(onNext: { print("遍历数组结果:",array[$0])})
    .disposed(by: disposeBag)

输出结果为:

遍历数组结果: 女孩1
遍历数组结果: 女孩2
遍历数组结果: 女孩3
遍历数组结果: 女孩4
遍历数组结果: 女孩5

8、repeatElement方法

生成无限次重复给定元素的可观察序列。

Observable<Int>.repeatElement(5)
    .subscribe { (event) in print("酒量:",event)}
    .disposed(by: disposeBag)

输出结果为:

酒量: next(5)
酒量: next(5)
酒量: next(5)
....无限次

9、error方法

返回一个以错误信息结束的可观察序列,比如请求网络失败时候发送的失败信号。

Observable<String>.error(NSError.init(domain: "error", code: 1997, userInfo: ["reason":"薪资太低"]))
    .subscribe { (event) in print("订阅:",event) }
    .disposed(by: disposeBag)

输出结果为:

订阅: error(Error Domain=error Code=1997 "(null)" UserInfo={reason=薪资太低})

10、never方法

永远不会发出事件也不会终止的可观察序列。

Observable<String>.never()
    .subscribe { (event) in print("兔子🐰,我保证永远都不变心,可以把屠龙宝刀放下了吗?",event) }
    .disposed(by: disposeBag)

无输出结果。


11、create方法

该方法接受一个闭包形式的参数用来对每一个订阅者发送消息,就好比用户订阅了微信公众号成了你的粉丝,你就要对他/她负责,每天发送优质内容给他/她阅读。

// 1.创建序列
let observable = Observable<String>.create
{ observer in
    // 3.发送消息
    // 对订阅者发出.next事件,且携带了一个数据"这是一篇富有..."
    observer.onNext("这是一篇富有启发性的有趣文章,希望你们喜欢")
    // 对订阅者发出.completed事件
    observer.onCompleted()
    // 因为一个订阅行为会有一个Disposable类型的返回值,所以在结尾一定要返回一个Disposable
    return Disposables.create()
}
 
// 2.订阅消息
_ = observable.subscribe{ print("粉丝阅读了公众号(漫游在云海的鲸鱼)发来的消息:\($0)") }

输出结果为:

粉丝阅读了公众号(漫游在云海的鲸鱼)发来的消息:next(这是一篇富有启发性的有趣文章,希望你们喜欢)
粉丝阅读了公众号(漫游在云海的鲸鱼)发来的消息:completed

四、高阶函数

1、使用Driver序列来请求网络

a、直接使用Observable序列进行网络请求
❶ 生成序列中的序列

textFiled序列中的text通过flatMap映射,得到调用dealwithData:方法返回的text序列,最终得到的result指的是text序列,而不是textFiled序列。

let result  = textFiled.rx.text.skip(1)
    .flatMap
    { [weak self](input) -> Observable<Any> in
        return (self?.dealwithData(inputText:input ?? ""))!
    }
❷ 将text转化为序列的函数

在这个函数中异步进行了网络请求,如果请求成功则将在textFiled中输入的text转变为"已经输入:\(inputText)",请求失败则返回错误信息,两种情况下都是作为创建的新序列返回。

func dealwithData(inputText:String)-> Observable<Any>
{
    print("开始请求网络了")
    
    // 创建了新序列
    return Observable<Any>.create (
    { (observable) -> Disposable in
        // 输入12345则请求网络失败
        if inputText == "12345"
        {
            observable.onError(NSError.init(domain: "com.xiejiapei.cn", code: 1997, userInfo: nil))
        }
        
        // 异步请求网络
        DispatchQueue.global().async
        {
            print("请求网络时候所处的线程为: \(Thread.current)")
            observable.onNext("已经输入\(inputText)")
            observable.onCompleted()
        }
        return Disposables.create()
    })
}
❸ 订阅了两次同样的信号
result
    .subscribe(onNext: { (element) in print("订阅到的内容为:\(element)")})
    .disposed(by: disposeBag)
    
result
    .subscribe(onNext: { (element) in print("订阅到的内容为:\(element),获得订阅内容时所在的线程为:\(Thread.current)")})
    .disposed(by: disposeBag)
❹ 输出正常的结果

可以看到由于我们对于同一信号订阅了两次,因此请求了两次网络,获得的却是同样的信息,这样就浪费了流量来进行不必要的重复请求。还有一个问题就是我们获取到订阅内容时候所处的线程为子线程,而不是能够更新UI的主线程,假如我们此时更新了UI就会出错。

开始请求网络了
开始请求网络了
请求网络时候所处的线程为: <NSThread: 0x600000202a80>{number = 6, name = (null)}
订阅到的内容为:已经输入1234
请求网络时候所处的线程为: <NSThread: 0x600000202a80>{number = 6, name = (null)}
订阅到的内容为:已经输入1234,获得订阅内容时所在的线程为:<NSThread: 0x600000202a80>{number = 6, name = (null)}
❺ 输出错误信息

按照常理来说,假如网络请求失败了,我们不应该将错误信息发送到订阅内容里,因为订阅内容是用于UI显示的,是直接拿到内容展示给用户观看的,而将error里面的错误信息直接展示给用户的话会让他暴跳如雷的,揍你的冲动都有了,所以我们需要针对错误信息进行处理,展示给用户他能够理解的提示框,比如网络出错了,请稍后重试等。

开始请求网络了
Unhandled error happened: Error Domain=com.xiejiapei.cn Code=1997 "(null)"
开始请求网络了
Unhandled error happened: Error Domain=com.xiejiapei.cn Code=1997 "(null)"
请求网络时候所处的线程为: <NSThread: 0x60000026dc40>{number = 9, name = (null)}
请求网络时候所处的线程为: <NSThread: 0x60000026dc40>{number = 9, name = (null)}

b、解决使用Observable序列进行网络请求时遇到的问题
❶ 添加share解决重复请求问题
let result  = textFiled.rx.text.skip(1)
    .flatMap
    { [weak self](input) -> Observable<Any> in
        return (self?.dealwithData(inputText:input ?? ""))!
    }
    .share(replay: 1, scope: .whileConnected)

输出结果为:

开始请求网络了
请求网络时候所处的线程为: <NSThread: 0x60000324fbc0>{number = 3, name = (null)}
订阅到的内容为:已经输入1234
订阅到的内容为:已经输入1234,获得订阅内容时所在的线程为:<NSThread: 0x60000324fbc0>{number = 3, name = (null)}

这样对于同样一个信号就只请求一次了。

❷ 添加MainScheduler解决子线程更新UI问题
let result  = textFiled.rx.text.skip(1)
    .flatMap
    { [weak self](input) -> Observable<Any> in
        return (self?.dealwithData(inputText:input ?? ""))!
            .observe(on: MainScheduler())
    }
    .share(replay: 1, scope: .whileConnected)

输出结果为:

开始请求网络了
请求网络时候所处的线程为: <NSThread: 0x6000002df080>{number = 6, name = (null)}
订阅到的内容为:已经输入1234
订阅到的内容为:已经输入1234,获得订阅内容时所在的线程为:<NSThread: 0x6000002e0280>{number = 1, name = main}

可以看到获得订阅内容时所在的线程已经是主线程了,这样就可以放心更新UI内容了。

❸ 添加catchAndReturn解决错误事件
let result  = textFiled.rx.text.skip(1)
    .flatMap
    { [weak self](input) -> Observable<Any> in
        return (self?.dealwithData(inputText:input ?? ""))!
            .observe(on: MainScheduler())
            .catchAndReturn("检测到了错误事件")
    }
    .share(replay: 1, scope: .whileConnected)

输出结果为:

开始请求网络了
订阅到的内容为:检测到了错误事件
订阅到的内容为:检测到了错误事件,获得订阅内容时所在的线程为:<NSThread: 0x600001cdcb00>{number = 1, name = main}
请求网络时候所处的线程为: <NSThread: 0x600001c9a040>{number = 5, name = (null)}

这样当网络请求失败的时候,订阅到的内容就不再是给编程人员使用的error信息,而是可以直接展示给用户,让用户能够理解的提示信息了。


c、使用Driver序列来请求网络

使用Observable序列进行网络请求显得较为繁琐,多了编程人员不关心的线程切换、错误提示的处理、重复网络请求等问题,所以可以使用更为简便的Driver序列来请求网络。

❶ 创建序列
let result = textFiled.rx.text.orEmpty
    .asDriver()
    .flatMap
    {
        return self
            .dealwithData(inputText: $0)
            .asDriver(onErrorJustReturn: "检测到了错误事件")
    }
❷ 订阅信号

由于drive不支持Any类型,所以使用map进行了类型转化。

result
    .map { "订阅到的内容的文本长度为: \(($0 as! String).count)"}
    .drive(self.label.rx.text)
result
    .map { "订阅到的内容为:\($0 as! String)"}
    .drive(self.button.rx.title())
❸ 输出结果为

Driver序列自动帮我们解决了线程切换、错误提示的处理、重复网络请求等问题。

开始请求网络了
请求网络时候所处的线程为: <NSThread: 0x60000259b4c0>{number = 9, name = (null)}

2、组合操作符

a、startWith

在开始从可观察源发出元素之前,发出指定的元素序列。

Observable.of("1", "2", "3", "4")
    .startWith("A")
    .startWith("B")
    .startWith("C", "a", "b")
    .subscribe(onNext: { print($0) })
    .disposed(by: disposeBag)

输出结果为:

CabBA1234
b、merge

将源可观察序列中的元素组合成一个新的可观察序列。

let subject1 = PublishSubject<String>()
let subject2 = PublishSubject<String>()

// 合并subject1和subject2
Observable.of(subject1, subject2)
    .merge()
    .subscribe(onNext: { print($0) })
    .disposed(by: disposeBag)

subject1.onNext("好")
subject1.onNext("想")
subject2.onNext("睡")
subject2.onNext("觉")
subject1.onNext("呀")
subject2.onNext("!")

输出结果结果为:

好
想
睡
觉
呀
!

c、zip

将多个源可观测序列组合成一个新的可观测序列,只有当多个序列同时有值的时候才会响应,否则只是存值。

let stringSubject = PublishSubject<String>()
let intSubject = PublishSubject<Int>()

Observable
    .zip(stringSubject, intSubject)
    { stringElement, intElement in
        "\(stringElement) \(intElement)"
    }
    .subscribe(onNext: { print($0) })
    .disposed(by: disposeBag)

// 在这里存储了内容,但是并不会响应,除非另一个也响应了
stringSubject.onNext("天王盖地虎")
stringSubject.onNext("宝塔镇河妖")

intSubject.onNext(1)
intSubject.onNext(2)
stringSubject.onNext("我爱米老鼠") // 再存一个内容
intSubject.onNext(3)

输出结果为:

天王盖地虎 1
宝塔镇河妖 2
我爱米老鼠 3

d、combineLatest

这个函数应用非常频繁,比如需要账户和密码同时满足才能进行登陆。

let stringSubject = PublishSubject<String>()
let intSubject = PublishSubject<Int>()

Observable.combineLatest(stringSubject, intSubject)
    { stringElement, intElement in
        "\(stringElement) \(intElement)"
    }
    .subscribe(onNext: { print($0) })
    .disposed(by: disposeBag)

// 存一个内容
stringSubject.onNext("我好笨啊,脑袋总是昏昏沉沉的")
// 再存一个内容的话会直接覆盖掉上一个内容,这点和zip不一样
stringSubject.onNext("羡慕别人思维逻辑那么清晰")
// 第一次响应
intSubject.onNext(1)
// 用2覆盖前面int的值1,再进行第二次响应
intSubject.onNext(2)
// 覆盖前面的内容再次响应
stringSubject.onNext("我要是也能聪明一点就好了")

输出结果为:

羡慕别人思维逻辑那么清晰 1
羡慕别人思维逻辑那么清晰 2
我要是也能聪明一点就好了 2
e、switchLatest

就像Github上面的branch分支一样,切换到哪个分支,就只对当前分支起作用。

let switchLatestSubject1 = BehaviorSubject(value: "普天之下")
let switchLatestSubject2 = BehaviorSubject(value: "1")
// 选择了 switchLatestSubject1 就不会监听 switchLatestSubject2
let switchLatestSubject  = BehaviorSubject(value: switchLatestSubject1)

switchLatestSubject.asObservable()
    .switchLatest()
    .subscribe(onNext: { print($0) })
    .disposed(by: disposeBag)

switchLatestSubject1.onNext("莫非王土")
switchLatestSubject1.onNext("率土之滨")

// 2-3都会不会监听,但是会保存,由2覆盖1,再由3覆盖2
switchLatestSubject2.onNext("2")
switchLatestSubject2.onNext("3")

// 切换到 switchLatestSubject2
switchLatestSubject.onNext(switchLatestSubject2)
// 不会再输出莫非王臣
switchLatestSubject1.onNext("莫非王臣")

switchLatestSubject2.onNext("4")

输出结果为:

普天之下
莫非王土
率土之滨
3
4

3、映射操作符

a、map

返回转换后的元素组成的新的可观察序列。

let observable = Observable.of(1,2,3,4)
observable.map
    { (number) -> Int in
        return number+2
    }
    .subscribe
    {
        print("\($0)")
    }
    .disposed(by: disposeBag)

输出结果为:

next(3)
next(4)
next(5)
next(6)
completed
b、flatMap

针对序列中序列:比如网络序列中还有模型序列。

let boy  = Player(score: 90)
let girl = Player(score: 100)
let player = BehaviorSubject(value: boy)

// player本身就是序列,这里再将player的score属性也转化为了序列
player.asObservable()
    .flatMap { $0.score.asObservable() }
    .subscribe(onNext: { print($0) })
    .disposed(by: disposeBag)

boy.score.onNext(60)
girl.score.onNext(80)

// 切换到女孩
player.onNext(girl)

boy.score.onNext(50)
girl.score.onNext(70)

输出结果为:

90
60
80
50
70

c、scan

从初始就带有一个默认值开始,然后对可观察序列发出的每个元素应用累加器闭包,并以单个元素可观察序列的形式返回每个中间结果。

Observable.of(10, 100, 1000)
    .scan(2)
    { aggregateValue, newValue in
        aggregateValue + newValue // 10 + 2 , 100 + 10 + 2 , 1000 + 100 + 2
    }
    .subscribe(onNext: { print($0) })
    .disposed(by: disposeBag)

输出结果为:

Observable.of(10, 100, 1000)
    .scan(2)
    { aggregateValue, newValue in
        aggregateValue + newValue // 10 + 2 , 100 + 10 + 2 , 1000 + 100 + 2
    }
    .subscribe(onNext: { print($0) })
    .disposed(by: disposeBag)

4、过滤条件操作符

a、filter

仅从满足指定条件的可观察序列中发出那些元素。

Observable.of(1,2,3,4,5,6,7,8,9,0)
    .filter { $0 % 2 == 0 }
    .subscribe(onNext: { print($0) })
    .disposed(by: disposeBag)

输出结果为:

2
4
6
8
0
b、distinctUntilChanged

剔除重复元素。

Observable.of("1", "2", "2", "2", "3", "3", "4")
    .distinctUntilChanged()
    .subscribe(onNext: { print($0) })
    .disposed(by: disposeBag)

输出结果为:

1
2
3
4
c、elementAt

只发出指定位置的元素的序列。

Observable.of("我", "好", "困", "惑", "呀")
    .element(at: 3)
    .subscribe(onNext: { print($0) })
    .disposed(by: disposeBag)

输出结果为:

d、single

只发出可观察序列中的第一个元素(或满足条件的第一个元素)。

Observable.of("棕色的长毛衣", "蓝色的牛仔裤", "黑色的皮靴")
    .single()
    .subscribe(onNext: { print($0) })
    .disposed(by: disposeBag)

Observable.of("棕色的长毛衣", "女朋友")
    .single { $0 == "女朋友" }
    .subscribe { print($0) }
    .disposed(by: disposeBag)

输出结果为:

棕色的长毛衣
next(女朋友)
completed
e、take

上面signal只有一个序列,在实际开发会受到局限,这里引出的take达到想几个就几个的效果。

Observable.of("好多呀", "学不完","想去玩", "天都黑了")
    .take(2)
    .subscribe(onNext: { print($0) })
    .disposed(by: disposeBag)

输出结果为:

好多呀
学不完
f、takeLast

仅从可观察序列的末尾发出指定数量的元素。

Observable.of("做框架的大哥", "你不累吗","我学得都疲惫了", "你创造的时候不嫌麻烦吗")
    .takeLast(2)
    .subscribe(onNext: { print($0) })
    .disposed(by: disposeBag)

输出结果为:

我学得都疲惫了
你创造的时候不嫌麻烦吗
g、takeWhile

当指定条件为true的时候才发出元素。

Observable.of(1, 2, 3, 4, 5, 6)
    .take(while: { (number) -> Bool in
        return number < 3
    })
    .subscribe(onNext: { print($0) })
    .disposed(by: disposeBag)

输出结果为:

1
2
h、takeUntil

应用非常频繁,比如页面销毁了就不能获取值了。

let sourceSequence = PublishSubject<String>()
let referenceSequence = PublishSubject<String>()

sourceSequence
    .take(until: referenceSequence)// 停止条件
    .subscribe { print($0) }
    .disposed(by: disposeBag)

sourceSequence.onNext("可乐")
sourceSequence.onNext("果粒橙")
sourceSequence.onNext("雪碧")

// 条件一出来,下面就不执行了
referenceSequence.onNext("芬达")

sourceSequence.onNext("咖啡")
sourceSequence.onNext("奶茶")
sourceSequence.onNext("果冻")

输出结果为:

next(可乐)
next(果粒橙)
next(雪碧)
completed
I、skip

每次看教材习题答案的时候最害怕看到的两个字:略过。

Observable.of(1, 2, 3, 4, 5, 6)
    .skip(2)
    .subscribe(onNext: { print($0) })
    .disposed(by: disposeBag)

输出结果为:

3
4
5
6
J、skipUntil
let sourceSeq = PublishSubject<String>()
let referenceSeq = PublishSubject<String>()

sourceSeq
    .skip(until: referenceSeq)
    .subscribe(onNext: { print($0) })
    .disposed(by: disposeBag)

// 没有满足条件,下面语句不会执行
sourceSeq.onNext("此刻你最想说什么")
sourceSeq.onNext("我有一句妈卖批")
sourceSeq.onNext("不知当讲不当讲")

// 条件一出来,就可以正常执行了
referenceSeq.onNext("条件")

sourceSeq.onNext("来个人")
sourceSeq.onNext("把作者给我拖出去")
sourceSeq.onNext("五花大绑 五马分尸 五雷轰顶")

输出结果为:

来个人
把作者给我拖出去
五花大绑 五马分尸 五雷轰顶

5、集合控制操作符

a、toArray

将一个可观察序列转换为一个数组。

Observable.range(start: 1, count: 10)
    .toArray()
    .subscribe { print($0) }
    .disposed(by: disposeBag)

输出结果为:

success([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
b、reduce

从一个设置的初始化值开始,然后对一个可观察序列发出的所有元素应用累加器闭包。

Observable.of(10, 100, 1000)
    .reduce(1, accumulator: +) // 1 + 10 + 100 + 1000 = 1111
    .subscribe(onNext: { print($0) })
    .disposed(by: disposeBag)

输出结果为:

1111
c、concat

以顺序方式连接元素。

let subject1 = BehaviorSubject(value: "瑞幸咖啡/神州优车集团")
let subject2 = BehaviorSubject(value: "挺感谢它的")

let subjectsSubject = BehaviorSubject(value: subject1)

subjectsSubject.asObservable()
    .concat()
    .subscribe { print($0) }
    .disposed(by: disposeBag)

subject1.onNext("留不住我")
subject1.onNext("不过还是")

subjectsSubject.onNext(subject2)

// 必须要等subject1完成了才能订阅到subject2,常用来控制执行顺序
subject1.onCompleted()
subject2.onNext("在我毕业的时候收留了即将流落街头的我")
subject2.onNext("不仅给我了一口饱饭还提供了一个温暖的私人房间")
subject2.onNext("工作的环境也挺好的")

输出结果为:

next(瑞幸咖啡/神州优车集团)
next(留不住我)
next(不过还是)
next(挺感谢它的)
next(在我毕业的时候收留了即将流落街头的我)
next(不仅给我了一口饱饭还提供了一个温暖的私人房间)
next(工作的环境也挺好的)

6、从可观察对象的错误通知中恢复的操作符

a、catchAndReturn

发送错误序列会返回预设的错误提示。

let sequenceThatFails = PublishSubject<String>()

sequenceThatFails
    .catchAndReturn("对不起,大脑死机了")
    .subscribe { print($0) }
    .disposed(by: disposeBag)

// 正常序列可以发送成功
sequenceThatFails.onNext("心情低落")
sequenceThatFails.onNext("没人关心")

// 发送错误序列会返回预设的错误提示
sequenceThatFails.onError(self.error)

输出结果为:

next(心情低落)
next(没人关心)
next(对不起,大脑死机了)
completed
b、catchError

通过切换到提供的恢复可观察序列,从错误事件中恢复。

let recoverySequence = PublishSubject<String>()
let sequenceThatFails = PublishSubject<String>()

sequenceThatFails
    .catch
    {
        // 获取到了错误序列后,在这里的闭包中处理完毕
        print("错误信息:", $0)
        
        // 返回给用户需要的序列(比如showAlert)
        return recoverySequence
    }
    .subscribe { print($0) }
    .disposed(by: disposeBag)

// 发送正常序列
sequenceThatFails.onNext("第二季一拳超人")
sequenceThatFails.onNext("一拳消灭了蜈蚣长老")
// 发送错误序列
sequenceThatFails.onError(error)

recoverySequence.onNext("太厉害,又太扯淡了")

输出结果为:

next(第二季一拳超人)
next(一拳消灭了蜈蚣长老)
错误信息: Error Domain=com.xiejiapei.cn Code=1997 "(null)"
next(太厉害,又太扯淡了)
c、retry

通过无限地重新订阅可观察序列来恢复重复的错误事件。

var count = 1// 重复条件
let sequenceRetryErrors = Observable<String>.create
{ observer in
    observer.onNext("蝴蝶效应")
    observer.onNext("恐怖游轮")
    observer.onNext("土拨鼠之日")
    
    if count < 3 // 结束条件
    {
        print("错误序列来了")
        
        // 接收到了错误序列,触发重试序列
        observer.onError(self.error)

        count += 1
    }
    
    observer.onNext("明日边缘")
    observer.onNext("时空旅行者的妻子")
    observer.onNext("永无止境")
    observer.onCompleted()
    
    return Disposables.create()
}

sequenceRetryErrors
    .retry()
    .subscribe(onNext: { print($0) })
    .disposed(by: disposeBag)

输出结果为:

蝴蝶效应
恐怖游轮
土拨鼠之日
错误序列来了
蝴蝶效应
恐怖游轮
土拨鼠之日
错误序列来了
蝴蝶效应
恐怖游轮
土拨鼠之日
明日边缘
时空旅行者的妻子
永无止境

上面是通过自己手动设置了结束条件count < 3来控制重复次数,也可以在.retry(3)里直接设置重试次数。


d、debug

打印所有订阅、事件和处理。

sequenceRetryErrors
    .retry()
    .debug()
    .subscribe(onNext: { print($0) })
    .disposed(by: disposeBag)

输出结果为:

恐怖游轮
2021-01-18 11:12:04.027: HigherOrderFunction.swift:558 (retry()) -> Event next(土拨鼠之日)
土拨鼠之日
错误序列来了
2021-01-18 11:12:04.027: HigherOrderFunction.swift:558 (retry()) -> Event next(蝴蝶效应)
......

7、链接操作符

connect

会同时向多个订阅发送同一消息。

let subject = PublishSubject<Any>()
subject.subscribe { print("00:\($0)") }
    .disposed(by: disposeBag)

let networkObservable = Observable<Any>.create
{ (observer) -> Disposable in
        sleep(2)// 模拟网络延迟
        print("我开始请求网络了")
    
        observer.onNext("请求到的网络数据")
        observer.onNext("请求到的本地")
        observer.onCompleted()
    
        return Disposables.create
        {
            print("销毁回调了")
        }
    }.publish()

networkObservable.subscribe(onNext:
    { (anything) in
        print("订阅1:",anything)
    })
    .disposed(by: disposeBag)

networkObservable.subscribe(onNext:
    { (anything) in
        print("订阅2:",anything)
    })
    .disposed(by: disposeBag)

_ = networkObservable.connect()

输出结果为:

我开始请求网络了
订阅1: 请求到的网络数据
订阅2: 请求到的网络数据
订阅1: 请求到的本地
订阅2: 请求到的本地
销毁回调了

五、既是序列也是观察者的Subject

1、PublishSubject

订阅之前的消息都会被忽略掉,比如我们订阅了微信公众号后,就只会接收到它之后发送的日更消息。

// 1:初始化序列
let publishSubject = PublishSubject<Int>()

// 2:发送信号(这里信号就是响应序列)
// 订阅之前的消息都会被忽略掉,所以不会输出发送的1
publishSubject.onNext(1)

// 3:订阅信号
publishSubject.subscribe { print("订阅到了:",$0)}
    .disposed(by: disposeBag)
// 4:再次发送信号(响应序列)
publishSubject.onNext(2)
publishSubject.onNext(3)

输出结果为:

订阅到了: next(2)
订阅到了: next(3)

2、BehaviorSubject

通过一个默认初始值来创建,当订阅者订阅BehaviorSubject时,会收到订阅后Subject上一个发出的Event,如果还没有收到任何数据,会发出一个默认值。之后就和PublishSubject一样,正常接收新的事件。和PublishSubject稍微不同就是BehaviorSubject这个家伙有个存储功能:存储上一次的信号。

// 1:创建序列
// 当没有信号的时候,会默认发送信号:100
let behaviorSubject = BehaviorSubject.init(value: 100)

// 2:发送信号
// 还没有订阅信号,这里只是保存
// 保存的时候后面的信号会覆盖掉前面的,导致前面的信号不再发送
behaviorSubject.onNext(2)
behaviorSubject.onNext(3)

// 3:订阅信号
behaviorSubject.subscribe{ print("过年 订阅到了:",$0)}
    .disposed(by: disposeBag)

// 再次发送
behaviorSubject.onNext(4)
behaviorSubject.onNext(5)

// 再次订阅,此时订阅到的信号是最后一次发出的信号
behaviorSubject.subscribe{ print("回家 订阅到了:",$0)}
    .disposed(by: disposeBag)

输出结果为:

过年 订阅到了: next(3)
过年 订阅到了: next(4)
过年 订阅到了: next(5)
回家 订阅到了: next(5)

3、ReplaySubject

开辟了一个bufferSize空间,可以指定保留信号的个数。

// 1:创建序列
// 2表示能够保存订阅序列之前的两个信号
let replaySubject = ReplaySubject<Int>.create(bufferSize: 2)

// 2:发送信号
replaySubject.onNext(1)
replaySubject.onNext(2)
replaySubject.onNext(3)
replaySubject.onNext(4)

// 3:订阅信号
replaySubject.subscribe{ print("订阅到了:",$0)}
    .disposed(by: disposeBag)

// 再次发送
replaySubject.onNext(7)
replaySubject.onNext(8)
replaySubject.onNext(9)

输出结果为:

订阅到了: next(3)
订阅到了: next(4)
订阅到了: next(7)
订阅到了: next(8)
订阅到了: next(9)

4、AsyncSubject

只发送由源Observable发送的最后一个事件。

// 1:创建序列
let asyncSubject = AsyncSubject<Int>.init()

// 2:发送信号
asyncSubject.onNext(1)
asyncSubject.onNext(2)

// 3:订阅信号
asyncSubject.subscribe{ print("订阅到了:",$0)}
    .disposed(by: disposeBag)

// 再次发送
asyncSubject.onNext(3)
asyncSubject.onNext(4)
//asyncSubject.onError(NSError.init(domain: "大学压迫", code: 10086, userInfo: nil))
asyncSubject.onCompleted()

输出结果为:

订阅到了: next(4)
订阅到了: completed

5、BehaviorRelay

可以避免再写繁琐的闭包。

let behaviorRelay = BehaviorRelay(value: 100)
print("不写闭包了:\(behaviorRelay.value)")
behaviorRelay.accept(1000)
print("天妒英才:\(behaviorRelay.value)")

输出结果为:

不写闭包了:100
不写闭包了:1000

六、场景序列

这个小节介绍平时开发经常运用的序列,这些序列都是由普通序列封装而来,但是能够简易流程。

1、controlEvent

let controlEventObservable = self.button.rx.controlEvent(.touchUpInside)

controlEventObservable
    .subscribe
    { (reslut) in
        print("订阅:\(reslut) \n \(Thread.current)")
    }
    .disposed(by: disposeBag)

输出结果为:

订阅:next(()) 
 <NSThread: 0x600003cdc9c0>{number = 1, name = main}

2、binder

// 将文本框的输入文本和Lable显示的文本绑定
self.textFiled.rx.text
    .bind(to: self.label.rx.text)
    .disposed(by: disposeBag)

七、综合案例

1、用户偏好设置

a、必选项:选择生日

必选项:出生日期不能早于今天否则边框变色。

let birthdayObservable = birthdayPicker.rx.date.map
{
    DataPickerValidator.isValidDate(date: $0)
}
birthdayObservable.map { $0 ? UIColor.orange : UIColor.blue}
    .subscribe(onNext: { color in self.birthdayPicker.layer.borderColor = color.cgColor} )
    .disposed(by: disposeBag)
b、必选项:选择性别
❶ 创建性别选择序列
let genderSelectObservable = BehaviorSubject<Gender>(value: .notSelcted)
❷ 男生按钮和性别选择序列进行绑定
maleButton.rx.tap
    .map { Gender.male }
    .bind(to: genderSelectObservable)
    .disposed(by: disposeBag)
❸ 女生按钮和性别选择序列进行绑定
femaleButton.rx.tap
    .map { Gender.female }
    .bind(to: genderSelectObservable)
    .disposed(by: disposeBag)
❹ 根据选择的性别更改显示的图片
genderSelectObservable.subscribe(onNext: { (gender) in
    switch gender
    {
    case .male:
        self.maleButton.setImage(UIImage(named: "check"), for: .normal)
        self.femaleButton.setImage(UIImage(named: "uncheck"), for: .normal)
    case .female:
        self.femaleButton.setImage(UIImage(named: "check"), for: .normal)
        self.maleButton.setImage(UIImage(named: "uncheck"), for: .normal)
    default:
        break;
    }
})
.disposed(by: disposeBag)
❺ 性别是否已经选择
let genderHasSelected = genderSelectObservable.map { $0 != .notSelcted ? true : false }
c、更新按钮是否可以点击

出生日期和性别都选择后更新按钮才可以点击。

Observable.combineLatest(birthdayObservable, genderHasSelected) { $0 && $1 }
    .bind(to: updateButton.rx.isEnabled)
    .disposed(by: disposeBag)
d、开关和滑条
开关

UISwitchOFF时,表示眼前的面试者不了解Swift,因此,下面的UISlider应该为0。当UISwitchON时,可以把UISlider设置在1/4的位置作为默认值,表示面试者了解一点皮毛。

knowSwiftSwitch.rx.value
    .map{ $0 ? 0.25 : 0}
    .bind(to: swiftLevelSlider.rx.value)
    .disposed(by: disposeBag)
滑条

UISlider不为0时,应该自动把UISwitch设置为ON,即面试者有了解过。当UISlider为0时,应该自动把UISwitch设置为OFF,表示面试者一头雾水。

swiftLevelSlider.rx.value
    .map { $0 != 0 ? true : false}
    .bind(to: knowSwiftSwitch.rx.isOn)
    .disposed(by: disposeBag)

e、计数器

通过计数器更改心跳的大小。

passionToLearnStepper.rx.value
    .skip(1)
    .subscribe(onNext: { (value) in
        UserPreferences.heartHeight = UserPreferences.heartHeight + 10
        self.heartImageView.frame = CGRect(x: 150, y: 750, width: UserPreferences.heartHeight, height: UserPreferences.heartHeight)
        self.heartImageView.setNeedsLayout()
    })
    .disposed(by: disposeBag)

2、Subject实战

a、使用传统方式实现Table里的增删改查操作
❶ 添加新cell
@objc func didClickAddAction()
{
    // 新行的index
    let newRowIndex = modelItems.count
    // 创建新行的数据
    let model = SubjectModel(title:"多喝牛奶可以长高哦:\(Date().timeIntervalSince1970)", isFinished: false)
    // 将新行的数据添加到数据列表中
    // 必需先提供了数据才能添加cell
    modelItems.append(model)
    
    // 将index转化为IndexPath
    let indexPath = IndexPath(row: newRowIndex, section: 0)
    // 根据IndexPath在table中插入新行
    tableView.insertRows(at: [indexPath], with: .automatic)
    
    // 将新行的数据添加到数据库中
    SubjectCacheManager.manager.insertModelToTable(models: [model])
}
❷ 删除cell
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath)
{
    // 从数据库中删除数据
    SubjectCacheManager.manager.deleteModelData(model: modelItems[indexPath.row])
    
    // 从数据列中删除数据
    modelItems.remove(at: indexPath.row)
    
    // 从table中删除cell
    let indexPaths = [indexPath]
    tableView.deleteRows(at: indexPaths, with: .automatic)
    tableView.deselectRow(at: indexPath, animated: true)
}
❸ 修改cell
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)
{
    let model = modelItems[indexPath.row]
    // 将是否完成状态进行反转
    model.toggleFinished()
    // 将完成状态反转后到model更新到数据库中
    SubjectCacheManager.manager.updataModelData(model: model)
    // 刷新table中状态反转的cell
    tableView.reloadRows(at: [indexPath], with: .fade)
    
    // 获取数据库中的所有数据打印出来
    let array = SubjectCacheManager.manager.fetachModelData()
    for item in array
    {
        print("\(item.title) --- \(item.isFinished)")
    }
}

b、使用Subject实现Table里的增删改查操作
❶ 创建序列
// 将从数据库中获取到的所有数据作为modelsSubject的初始值
modelsSubject = BehaviorSubject(value: SubjectCacheManager.manager.fetachModelData())
modelsSubject?.subscribe(onNext:
{ [weak self](items) in
    self?.modelItems = items// 将获取的所有数据赋值给列表
    self?.tableView.reloadData()// 刷新表视图
    self?.deleteButton.isEnabled = !items.isEmpty// items非空的话删除按钮便可点击
    SubjectCacheManager.manager.updataAllData(models: items)// 根据items更新数据库
    
    // 列表中没有完成的待办事项的数量
    let num = items.filter(
    { (model) -> Bool in
        return !model.isFinished
    }).count
    // 待办事项的数量小于6,导航栏的添加按钮便可点击
    self?.navigationItem.rightBarButtonItem?.isEnabled = num <= 6
    // 更新标题
    self?.title = num == 0 ? "Subject实战" : "还有 \(num) 条待办事项"
}).disposed(by: disposeBag)
❷ 发送添加cell信号
@objc func didClickAddAction()
{
    let newRowIndex = modelItems.count
    let indexPath = IndexPath(row: newRowIndex, section: 0)
    let model = SubjectModel(title:"多喝牛奶可以长高哦:\(Date().timeIntervalSince1970)", isFinished: false)
    modelItems.append(model)
    tableView.insertRows(at: [indexPath], with: .automatic)
    
    // 将添加新行后的列表作为信号发送出去
    modelsSubject?.onNext(modelItems)
}
❸ 发送删除cell信号
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath)
{
    // 将删除cell后的列表作为信号发送出去
    modelItems.remove(at: indexPath.row)
    modelsSubject?.onNext(modelItems)
    tableView.deselectRow(at: indexPath, animated: true)
}
❹ 发送修改cell信号
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)
{
    let model = modelItems[indexPath.row]
    
    // 将完成状态反转后的列表作为信号发送出去
    model.toggleFinished()
    modelsSubject?.onNext(modelItems)
}

c、使用Subject实现详情向列表界面传值
❶ 创建序列。由列表界面订阅信号,在详情界面里发起响应
// todoSubject只在这个文件可用,因为只有在详情界面才更改待办事项的名字
fileprivate let todoSubject = PublishSubject<SubjectModel>()
// todoObservable提供给外界订阅详情中的待办事项获取其数据,但是不能对其进行更改
var todoObservable: Observable<SubjectModel>
{
    return todoSubject.asObservable()
}
❷ 判断进入详情界面是要新增还是展示
override func viewWillAppear(_ animated: Bool)
{
    super.viewWillAppear(animated)
    
    todoNameTextField.becomeFirstResponder()
    
    // 点击列表面里的cell进入详情界面需要显示数据
    if let model = model
    {
        todoNameTextField.text = model.tittle
        isFinishedSwitch.isOn = model.isFinished
    }
    // 点击新增cell进入详情界面无数据
    else
    {
        model = SubjectModel(tittle: "", isFinished: false)
    }
}
❸ 点击完成按钮后将model作为信号发送到上一个列表界面
@objc func didClickDoneAction()
{
    // 点击完成按钮后将model作为信号发送到上一个列表界面
    model.tittle = todoNameTextField.text ?? ""
    model.isFinished = isFinishedSwitch.isOn
    todoSubject.onNext(model)
    self.navigationController?.popViewController(animated: true)
}
❹ 订阅信号后在回调闭包中更新UI
fileprivate func pushToDetailVC(model:SubjectModel?)
{
    let detailVC = SubjectDetailViewController.init()
    if let model = model
    {
        detailVC.model = model
    }
    
    // 订阅信号后在回调闭包中更新UI
    detailVC.todoObservable.subscribe(onNext:
    { [weak self](model) in
        self?.modelItems.append(model)
        self?.modelsSubject?.onNext(self?.modelItems ?? [])
    }).disposed(by: disposeBag)
    
    self.navigationController?.pushViewController(detailVC, animated: true)
}
❺ 从列表界面进入详情界面

通过点击列表中的单元格进入详情界面。

func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)
{
    // 进入详情界面修改cell
    let model = modelItems[indexPath.row]
    pushToDetailVC(model: model)
}

点击添加按钮进入详情界面。

@objc func didClickAddAction()
{
    // 进入详情界面添加cell
    pushToDetailVC(model: nil)
}

Demo

Demo在我的Github上,欢迎下载。
UseFrameworkDemo

参考文献

上一篇 下一篇

猜你喜欢

热点阅读