RxSwift 使用方式
背景
最近项目业务,所有模块已经支持Swift混编开发,正在逐步使用Swift 方式进行开发新业务,以及逐步替换老业务方式进行发展,所以使用一些较为成熟的Swift 的三方库,成为必要性,经过调研发现RxSwift 在使用的情况上,较为成熟,且方便实用;以下介绍一些RxSwift 使用的一些方式和大家一起学习讨论
Swift为值类型,在传值与方法回调上有影响,RxSwift一定程度上弥补Swift的灵活性
- RxSwift使得代码复用性较强,减少代码量
- RxSwift因为声明都是不可变更,增加代码可读性
- RxSwift使得更易于理解业务代码,抽象异步编程,统一代码风格
- RxSwift使得代码更易于编写集成单元测试,增加代码稳定性
一:简单的使用方式
1.1 信号创建
1.1.1创建一个普通信号
let disposeB = DisposeBag()//通过指定的方法实现来自定义一个被观察的序列。//订阅创建let myOb = Observable<Any>.create { (observ) -> Disposable inobserv.onNext("alan")observ.onCompleted()return Disposables.create()}//订阅事件myOb.subscribe { (even) inprint("subscribe" + "\(even)")}.disposed(by: disposeB)//销毁1.1.2 创建信号的其他方式class ViewController: UIViewController {let disposeB = DisposeBag()override func viewDidLoad() {super.viewDidLoad()// Do any additional setup after loading the view, typically from a nib.//通过指定的方法实现来自定义一个被观察的序列。//订阅创建let myOb = Observable<Any>.create { (observ) -> Disposable inobserv.onNext("alan")zipSigal observ.onCompleted()return Disposables.create()}//订阅事件myOb.subscribe { (even) inprint("subscribe" + "\(even)")}.disposed(by: disposeB)//销毁//各种观察者序列产生方式//该方法通过传入一个默认值来初始化。Observable.just("just").subscribe { (event) inprint(event)}.disposed(by: disposeB)//该方法可以接受可变数量的参数(必需要是同类型的)Observable.of("o","f","of").subscribe { (event) inprint(event)}.disposed(by: disposeB)//该方法需要一个数组参数。Observable.from(["f","r","o","m"]).subscribe { (event) inprint("from" + "\(event)")}.disposed(by: disposeB)//该方法创建一个永远不会发出 Event(也不会终止)的 Observable 序列。Observable<Int>.never().subscribe { (event) inprint(event)}.disposed(by: disposeB)// // 该方法创建一个空内容的 Observable 序列。 //会打印completeObservable<Int>.empty().subscribe { (event) inprint("empty" ,event)}.disposed(by: disposeB)//该方法创建一个不做任何操作,而是直接发送一个错误的 Observable 序列。let myError = MyError.Aprint(myError.errorType)Observable<Int>.error(myError).subscribe { (event) inprint(event.error)}.disposed(by: disposeB)//该方法通过指定起始和结束数值,创建一个以这个范围内所有值作为初始值的Observable序列。Observable.range(start: 1, count: 6).subscribe { (event) inprint(event)}.disposed(by: disposeB)//该方法创建一个可以无限发出给定元素的 Event的 Observable 序列(永不终止)。慎重使用// Observable.repeatElement("SPAlan").subscribe { (event) in// print(event)// }.disposed(by: disposeB)//该方法创建一个只有当提供的所有的判断条件都为 true 的时候,才会给出动作的 Observable 序列。//第一个参数:初始化的数值 第二个 条件 第三也是一个条件 0 + 2 <= 10 依次循环下去,iterate:重复执行 ,执行结果为 0,2,4,6,8,10Observable.generate(initialState: 0, condition: {$0<=10}, iterate: {$0+2}).subscribe { (event) inprint(event)}.disposed(by: disposeB)//上面和下面的效果一样Observable.of(0,2,4,6,8,10).subscribe { (event) inprint(event)}.disposed(by: disposeB)//该个方法相当于是创建一个 Observable 工厂,通过传入一个 block 来执行延迟 Observable序列创建的行为,而这个 block 里就是真正的实例化序列对象的地方。var isOdd = truelet factory: Observable<Int> = Observable.deferred { () -> Observable<Int> inisOdd = !isOddif isOdd{return Observable.of(0,2,4,6,8)}else{return Observable.of(1,3,5,7,9)}}//这里会调用上面的工厂factory.subscribe { (event) inprint("\(isOdd)",event)}.disposed(by: disposeB)//这里会再次调用工厂factory.subscribe { (event) inprint("\(isOdd)",event)}.disposed(by: disposeB)//这个方法创建的 Observable 序列每隔一段设定的时间,会发出一个索引数的元素。而且它会一直发送下去。// Observable<Int>.interval(DispatchTimeInterval.seconds(1), scheduler: MainScheduler.instance).subscribe { (event) in// print(event)// }.disposed(by: disposeB)//这个方法有两种用法,一种是创建的 Observable序列在经过设定的一段时间后,产生唯一的一个元素。,这个只调用一次Observable<Int>.timer(DispatchTimeInterval.seconds(1), scheduler: MainScheduler.instance).subscribe{(event) inprint("123",event)}.disposed(by: disposeB)//另一种是创建的 Observable 序列在经过设定的一段时间后,每隔一段时间产生一个元素。:经过5秒后每个1秒创建一个元素// Observable<Int>.timer(DispatchTimeInterval.seconds(5), period: DispatchTimeInterval.seconds(1), scheduler: MainScheduler.instance).subscribe { (event) in// print(event)// }.disposed(by: disposeB)}enum MyError:Error {case Acase Bvar errorType:String {switch self {case .A:return "i am error A"case .B:return "BBBB"}}}override func didReceiveMemoryWarning() {super.didReceiveMemoryWarning()// Dispose of any resources that can be recreated.}}
1.2 iOS中的一些用法
1.2.1:button点击事件
//MARK: - RxSwift应用-button响应func setupButton() {// 业务逻辑 和 功能逻辑// 设计self.button.rx.tap.subscribe(onNext: { () inprint("按钮点击了")}).disposed(by: disposeBag)}
1.2.2:textfiled文本响应
//MARK: - RxSwift应用-textfiledfunc setupTextFiled() {self.textFiled.rx.text.orEmpty.subscribe(onNext: { (text) inprint(text)}).disposed(by: disposeBag)// 简单简单 更简单-RxSwift 面向开发者self.textFiled.rx.text.bind(to: self.button.rx.title()).disposed(by: disposeBag)}
1.2.3:scrollView效果
//MARK: - RxSwift应用-scrollViewfunc setupScrollerView() {scrollView.contentSize = CGSize(width: 300, height: 1000)scrollView.rx.contentOffset.subscribe(onNext: { [weak self](content) inprint(content);print(self?.scrollView)}).disposed(by: disposeBag)}
1.2.4:KVO
//MARK: - RxSwift应用-KVOfunc setupKVO() {// 系统KVO 还是比较麻烦的// person.addObserver(self, forKeyPath: "name", options: .new, context: nil)person.rx.observeWeakly(String.self, "name").subscribe(onNext: { (change) inprint(change ?? "helloword")}).disposed(by: disposeBag)}
1.2.5:通知
//MARK: - 通知func setupNotification(){NotificationCenter.default.rx.notification(UIResponder.keyboardWillShowNotification).subscribe { (event) inprint(event)}.disposed(by: disposeBag)}
1.2.6:手势
//MARK: - 手势func setupGestureRecognizer(){let tap = UITapGestureRecognizer()self.label.addGestureRecognizer(tap)self.label.isUserInteractionEnabled = truetap.rx.event.subscribe(onNext: { (tap) inprint(tap.view)}).disposed(by: disposeBag)}
1.2.7:网络请求
//MARK: - RxSwift应用-网络请求func setupNextwork() {let url = URL(string: "https://www.baidu.com")// 传统方式URLSession.shared.dataTask(with: url!) { (data, response, error) inprint("dataTask.response->" + "\(response)")print("dataTask.response->" + "\(data)")}.resume()//RxSwift 方式URLSession.shared.rx.response(request: URLRequest(url: url!)).subscribe(onNext: { (response, data) inprint("rx.response.response ==== \(response)")print("rx.response.data ===== \(data)")}, onError: { (error) inprint("error ===== \(error)")}).disposed(by: disposeBag)}
1.2.8:timer定时器
func setupTimer() {timer = Observable<Int>.interval(DispatchTimeInterval.seconds(1), scheduler: MainScheduler.instance)timer.subscribe(onNext: { (num) inprint("hello word \(num)")}).disposed(by: disposeBag)}
二:高阶用法
2.1:组合操作符
2.1.1:startWith
print("*****startWith*****")Observable.of("1", "2", "3", "4").startWith("A").startWith("B").startWith("C", "a", "b").subscribe(onNext: { print($0) }).disposed(by: disposeBag)//效果: CabBA1234
2.1.2:merge
print("*****merge*****")let subject1 = PublishSubject<String>()let subject2 = PublishSubject<String>()// merge subject1和subject2Observable.of(subject1, subject2).merge().subscribe(onNext: { print($0) }).disposed(by: disposeBag)subject1.onNext("C")subject1.onNext("o")subject2.onNext("o")subject2.onNext("o")subject1.onNext("c")subject2.onNext("i")
2.1.3:zip
- 将多达8个原可观测序列组合成一个新的可观测序列,并将从组合的可观测序列中发射出对应索引处每个原可观测序列的元素
print("*****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("C")stringSubject.onNext("o") // 到这里存储了 C o 但是不会响应除非;另一个响应intSubject.onNext(1) // 勾出一个intSubject.onNext(2) // 勾出另一个stringSubject.onNext("i") // 存一个intSubject.onNext(3) // 勾出一个// 说白了: 只有两个序列同时有值的时候才会响应,否则存值
2.1.4:combineLatest
- 将原可观测序列组合成一个新的观测序列,并将开始发出联合观测序列的每个元素最新元素可观测序列一旦所有排放原序列至少有一个元素,并且当原可观测序列发出的任何一个新元素
print("*****combineLatest*****")let stringSub = PublishSubject<String>()let intSub = PublishSubject<Int>()Observable.combineLatest(stringSub, intSub) { strElement, intElement in"\(strElement) \(intElement)"}.subscribe(onNext: { print($0) }).disposed(by: disposeBag)stringSub.onNext("S") // 存一个 SstringSub.onNext("P") // 存了一个覆盖 - 和zip不一样intSub.onNext(1) // 发现strOB也有G 响应 G 1intSub.onNext(2) // 覆盖1 -> 2 发现strOB 有值G 响应 G 2stringSub.onNext("alan") // 覆盖G -> alan 发现intOB 有值2 响应 alan 2// combineLatest 比较zip 会覆盖// 应用非常频繁: 比如账户和密码同时满足->才能登陆. 不关系账户密码怎么变化的只要查看最后有值就可以 loginEnable输出结果:*****combineLatest*****P 1P 2alan 2
2.1.5:switchLatest
- 将可观察序列发出的元素转换为可观察序列,并从最近的内部可观察序列发出元素
print("*****switchLatest*****")let switchLatestSub1 = BehaviorSubject(value: "L")let switchLatestSub2 = BehaviorSubject(value: "1")let switchLatestSub = BehaviorSubject(value: switchLatestSub1)// 选择了 switchLatestSub1 就不会监听 switchLatestSub2switchLatestSub.asObservable().switchLatest().subscribe(onNext: { print($0) }).disposed(by: disposeBag)switchLatestSub1.onNext("G")switchLatestSub1.onNext("_")switchLatestSub2.onNext("2")switchLatestSub2.onNext("3") // 2-3都会不会监听,但是默认保存由 2覆盖1 3覆盖2switchLatestSub.onNext(switchLatestSub2) // 切换到 switchLatestSub2switchLatestSub1.onNext("*")switchLatestSub1.onNext("alan") // 原理同上面 下面如果再次切换到 switchLatestSub1会打印出 alanswitchLatestSub2.onNext("4")输出结果为:*****switchLatest*****LG_34
2.2:映射操作符
2.2.1:map
- 转换闭包应用于可观察序列发出的元素,并返回转换后的元素的新可观察序列。
print("*****map*****")let ob = Observable.of(1,2,3,4)ob.map { (number) -> Int inreturn number+2}.subscribe{print("\($0)")}.disposed(by: disposeBag)
2.2.2:flatMap and flatMapLatest
- 将可观测序列发射的元素转换为可观测序列,并将两个可观测序列的发射合并为一个可观测序列。
- 这也很有用,例如,当你有一个可观察的序列,它本身发出可观察的序列,你想能够对任何一个可观察序列的新发射做出反应(序列中序列:比如网络序列中还有模型序列)
- flatMap和flatMapLatest的区别是,flatMapLatest只会从最近的内部可观测序列发射元素
struct SPPlayer {var score: BehaviorRelay<Int>}func flatMapLatest(){print("*****flatMapLatest*****")var boy = SPPlayer(score: BehaviorRelay(value: 100))var girl = SPPlayer(score: BehaviorRelay(value: 90))var player = BehaviorSubject(value: boy)player.asObservable().flatMapLatest{$0.score.asObservable() } // 本身score就是序列 模型就是序列中的序列.subscribe(onNext: { print($0) }).disposed(by: disposeBag)boy.score.accept(75)player.onNext(girl)boy.score.accept(50)boy.score.accept(40)// 如果切换到 flatMapLatest 就不会打印girl.score.accept(10)girl.score.accept(0)// 输出结果为:100,75,90,10,0}func flatMap(){print("*****flatMap*****")var boy = SPPlayer(score: BehaviorRelay(value: 100))var girl = SPPlayer(score: BehaviorRelay(value: 90))var player = BehaviorSubject(value: boy)player.asObservable().flatMap {$0.score.asObservable() } // 本身score就是序列 模型就是序列中的序列.subscribe(onNext: { print($0) }).disposed(by: disposeBag)boy.score.accept(75)player.onNext(girl)boy.score.accept(50)boy.score.accept(40)// 如果切换到 flatMapLatest 就不会打印girl.score.accept(10)girl.score.accept(0)// 输出结果为:100,75,90,50,40,10,0}
2.2.3:scan
- 从初始就带有一个默认值开始,然后对可观察序列发出的每个元素应用累加器闭包,并以单个元素可观察序列的形式返回每个中间结果
print("*****scan*****")Observable.of(10, 100, 1000).scan(2) { aggregateValue, newValue inaggregateValue + newValue // 10 + 2 , 100 + 10 + 2 , 1000 + 100 + 2}.subscribe(onNext: { print($0) }).disposed(by: disposeBag)// 这里主要强调序列值之间的关系
2.3:过滤条件操作符
2.3.1:filter
- 仅从满足指定条件的可观察序列中发出那些元素
print("*****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.3.2:distinctUntilChanged
- 抑制可观察序列发出的顺序重复元素
print("*****distinctUntilChanged*****")Observable.of("1", "2", "2", "2", "3", "3", "4").distinctUntilChanged().subscribe(onNext: { print($0) }).disposed(by: disposeBag)
2.3.3:elementAt
- 仅在可观察序列发出的所有元素的指定索引处发出元素
print("*****elementAt*****")Observable.of("A", "l", "a", "n", "z").element(at: 3).subscribe(onNext: { print($0) }).disposed(by: disposeBag)
2.3.4:single
- 只发出可观察序列发出的第一个元素(或满足条件的第一个元素)。如果可观察序列发出多个元素,将抛出一个错误。
print("*****single*****")Observable.of("alan", "zhi").single().subscribe(onNext: { print($0) }).disposed(by: disposeBag)Observable.of("zhai", "zhuang").single { $0 == "zhuang" }.subscribe (onNext:{ print($0,"---single---") }).disposed(by: disposeBag)/**输出结果:alanUnhandled error happened: Sequence contains more than one element.zhuang ---single---**/
2.3.5:take
- 只从一个可观察序列的开始发出指定数量的元素。 上面signal只有一个序列 在实际开发会受到局限 这里引出 take 想几个就几个
print("*****take*****")Observable.of("alan", "zhuang","xing", "zhai").take(2).subscribe(onNext: { print($0) }).disposed(by: disposeBag)
2.3.6:takeLast
- 仅从可观察序列的末尾发出指定数量的元素
print("*****takeLast*****")Observable.of("alan", "zhuang","xing", "zhai").takeLast(3).subscribe(onNext: { print($0) }).disposed(by: disposeBag)//输出结果:zhuang xing zhai2.3.7:takeWhile
只要指定条件的值为true,就从可观察序列的开始发出元素print("*****takeWhile*****")Observable.of(1, 2, 3, 4, 5, 6).take(while: { int inint < 3}).subscribe(onNext: { print($0) }).disposed(by: disposeBag)// 输出结果 1,2
2.3.8:takeUntil
- 从原序列可观察序列发出元素,直到参考可观察序列发出元素
- 这个要重点,应用非常频繁 比如当前页面销毁了,就不能获取值了(cell重用运用)
print("*****takeUntil*****")let sourceSequence = PublishSubject<String>()let referenceSequence = PublishSubject<String>()sourceSequence.take(until: referenceSequence).subscribe (onNext:{ print($0) }).disposed(by: disposeBag)sourceSequence.onNext("alan")sourceSequence.onNext("zhai")sourceSequence.onNext("xing")referenceSequence.onNext("zhuang") // 条件一出来,下面就走不了sourceSequence.onNext("qiang")sourceSequence.onNext("yanzi")sourceSequence.onNext("ting")输出结果为:*****takeUntil*****alanzhaixing
2.3.9:skip
- 从原序列可观察序列发出元素,直到参考可观察序列发出元素
- 这个要重点,应用非常频繁 不用解释 textfiled 都会有默认序列产生
print("*****skip*****")Observable.of(1, 2, 3, 4, 5, 6).skip(2).subscribe(onNext: { print($0) }).disposed(by: disposeBag)print("*****skipWhile*****")Observable.of(1, 2, 3, 4, 5, 6).skip(while :{int inint < 3}).subscribe(onNext: { print($0) }).disposed(by: disposeBag)
2.3.10:skipUntil
- 抑制从原序列可观察序列发出元素,直到参考可观察序列发出元素
print("*****skipUntil*****")let sourceSeq = PublishSubject<String>()let referenceSeq = PublishSubject<String>()sourceSeq.skip(until: referenceSeq).subscribe(onNext: { print($0) }).disposed(by: disposeBag)// 没有条件命令 下面走不了sourceSeq.onNext("alan")sourceSeq.onNext("zhai")sourceSeq.onNext("qiang")referenceSeq.onNext("zhuang") // 条件一出来,下面就可以走了sourceSeq.onNext("lilei")sourceSeq.onNext("yanzi")sourceSeq.onNext("meimei")输出结果为:*****skipUntil*****lileiyanzimeimei
2.4:集合控制操作符
2.4.1:toArray
- 将一个可观察序列转换为一个数组,将该数组作为一个新的单元素可观察序列发出,然后终止
print("*****toArray*****")Observable.range(start: 1, count: 10).toArray().subscribe(onSuccess: { print($0) }).disposed(by: disposeBag)// 输出结果:[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
2.4.2:reduce
- 从一个设置的初始化值开始,然后对一个可观察序列发出的所有元素应用累加器闭包,并以单个元素可观察序列的形式返回聚合结果 - 类似scan
print("*****reduce*****")Observable.of(10, 100, 1000).reduce(1, accumulator: +) // 1 + 10 + 100 + 1000 = 1111.subscribe(onNext: { print($0) }).disposed(by: disposeBag)// 输出结果:1111
2.4.3:concat
- 以顺序方式连接来自一个可观察序列的内部可观察序列的元素,在从下一个序列发出元素之前,等待每个序列成功终止
print("*****concat*****")let subject1 = BehaviorSubject(value: "zhai")let subject2 = BehaviorSubject(value: "1")let subjectsSubject = BehaviorSubject(value: subject1)subjectsSubject.asObservable().concat().subscribe (onNext:{ print($0) }).disposed(by: disposeBag)subject1.onNext("alan")subject1.onNext("xing")subjectsSubject.onNext(subject2)subject2.onNext("打印不出来")subject2.onNext("2")subject1.onCompleted() // 必须要等subject1 完成了才能订阅到! 用来控制顺序 网络数据的异步subject2.onNext("3")
2.5:从可观察对象的错误通知中恢复的操作符
2.5.1:catchAndReturn
- 从错误事件中恢复,方法是返回一个可观察到的序列,该序列发出单个元素,然后终止
enum SPError:Error {case Acase Bvar errorType:String {switch self {case .A:return "i am error A"case .B:return "BBBB"}}}let spError = SPError.Aprint("*****catchAndReturn*****")let sequenceThatFails = PublishSubject<String>()sequenceThatFails.catchAndReturn("alan").subscribe (onNext: { print($0) }).disposed(by: disposeBag)sequenceThatFails.onNext("qiang")sequenceThatFails.onNext("lilei") // 正常序列发送成功的//发送失败的序列,一旦订阅到位 返回我们之前设定的错误的预案sequenceThatFails.onError(self.spError)
5.2:catch
- 通过切换到提供的恢复可观察序列,从错误事件中恢复
print("*****catch*****")let sequenceThatFails = PublishSubject<String>()let recoverySequence = PublishSubject<String>()sequenceThatFails.catch {print("Error:", $0)return recoverySequence}.subscribe { print($0) }.disposed(by: disposeBag)sequenceThatFails.onNext("😬")sequenceThatFails.onNext("😨")sequenceThatFails.onNext("😡")sequenceThatFails.onNext("🔴")sequenceThatFails.onError(self.spError)recoverySequence.onNext("😊")5.3:retry
通过无限地重新订阅可观察序列来恢复重复的错误事件print("*****retry*****")var count = 1 // 外界变量控制流程let sequenceRetryErrors = Observable<String>.create { observer inobserver.onNext("alan")observer.onNext("xing")observer.onNext("zhai")if count == 1 {// 流程进来之后就会过度-这里的条件可以作为出口,失败的次数observer.onError(self.spError) // 接收到了错误序列,重试序列发生print("错误序列来了")count += 1}observer.onNext("liLie")observer.onNext("yanzi")observer.onNext("ting")observer.onCompleted()return Disposables.create()}sequenceRetryErrors.retry().subscribe(onNext: { print($0) }).disposed(by: disposeBag)/***输出结果:*****retry*****alanxingzhai错误序列来了alanxingzhailiLieyanziting*/
5.4:retry(_:):
- 通过重新订阅可观察到的序列,重复地从错误事件中恢复,直到重试次数达到max未遂计数
print("*****retry(_:)*****")var count = 1let sequenceThatErrors = Observable<String>.create { observer inobserver.onNext("alan")observer.onNext("xing")observer.onNext("qiangqiang")if count < 5 { // 这里设置的错误出口是没有太多意义的额,因为我们设置重试次数observer.onError(self.spError)print("错误序列来了")count += 1}observer.onNext("liLei")observer.onNext("yanzi")observer.onNext("ting")observer.onCompleted()return Disposables.create()}sequenceThatErrors.retry(3).subscribe(onNext: { print($0) }).disposed(by: disposeBag)
2.6:Rx流程操作符。
2.6.1:debug
- 打印所有订阅、事件和处理。
print("*****debug*****")var count = 1let sequenceThatErrors = Observable<String>.create { observer inobserver.onNext("alan")observer.onNext("zhai")observer.onNext("qiang")if count < 5 {observer.onError(self.spError)print("错误序列来了")count += 1}observer.onNext("liLei")observer.onNext("yanzi")observer.onNext("zhuang")observer.onCompleted()return Disposables.create()}sequenceThatErrors.retry(3).debug().subscribe(onNext: { print($0) }).disposed(by: disposeBag)
2.7:链接操作符
2.7.1:multicast
- 将原可观察序列转换为可连接序列,并通过指定的主题广播发射。
func testMulticastConnectOperators(){print("*****multicast*****")let netOB = Observable<Any>.create { (observer) -> Disposable insleep(2)// 模拟网络延迟print("我开始请求网络了")observer.onNext("请求到的网络数据")observer.onNext("请求到的本地")observer.onCompleted()return Disposables.create {print("销毁回调了")}}.publish()netOB.subscribe(onNext: { (anything) inprint("订阅1:",anything)}).disposed(by: disposeBag)// 我们有时候不止一次网络订阅,因为有时候我们的数据可能用在不同的地方// 所以在订阅一次 会出现什么问题?netOB.subscribe(onNext: { (anything) inprint("订阅2:",anything)}).disposed(by: disposeBag)/**输出结果为:*****multicast*****我开始请求网络了订阅1: 请求到的网络数据订阅2: 请求到的网络数据订阅1: 请求到的本地订阅2: 请求到的本地销毁回调了**/_ = netOB.connect()}
2.7.2:replay
- 将原可观察序列转换为可连接的序列,并将向每个新订阅服务器重放以前排放的缓冲大小
- 首先拥有和publish一样的能力,共享 Observable sequence, 其次使用replay还需要我们传入一个参数(buffer size)来缓存已发送的事件,当有新的订阅者订阅了,会把缓存的事件发送给新的订阅者
print("*****replay*****")let intSequence = Observable<Int>.interval(.seconds(1), scheduler: MainScheduler.instance).replay(5)_ = intSequence.subscribe(onNext: { print(Date(),"订阅 1:, Event: \($0)") })DispatchQueue.main.asyncAfter(deadline: .now() + 2) {_ = intSequence.connect()}DispatchQueue.main.asyncAfter(deadline: .now() + 4) {_ = intSequence.subscribe(onNext: { print(Date(),"订阅 2:, Event: \($0)") })}DispatchQueue.main.asyncAfter(deadline: .now() + 8) {_ = intSequence.subscribe(onNext: { print(Date(),"订阅 3:, Event: \($0)") })}DispatchQueue.main.asyncAfter(deadline: .now() + 10) {self.disposeBag = DisposeBag()}
三:示例
3.1:简单实用RxSwift 做一个计时器功能,控件绑定数据基础用法
计时器源码
文章Demo源码
相关文章:

RxSwift 使用方式
背景 最近项目业务,所有模块已经支持Swift混编开发,正在逐步使用Swift 方式进行开发新业务,以及逐步替换老业务方式进行发展,所以使用一些较为成熟的Swift 的三方库,成为必要性,经过调研发现RxSwift 在使用…...

HTML5 Web Worker
HTML5 Web Worker是一种浏览器提供的JavaScript多线程解决方案,它允许在后台运行独立于页面主线程的脚本,从而避免阻塞页面的交互和渲染。Web Worker可以用于执行计算密集型任务、处理大量数据、实现并行计算等,从而提升前端应用的性能和响应…...

25.9 matlab里面的10中优化方法介绍—— 惩罚函数法求约束最优化问题(matlab程序)
1.简述 一、算法原理 1、问题引入 之前我们了解过的算法大部分都是无约束优化问题,其算法有:黄金分割法,牛顿法,拟牛顿法,共轭梯度法,单纯性法等。但在实际工程问题中,大多数优化问题都属于有约…...

django channels实战(websocket底层原理和案例)
1、websocket相关 1.1、轮询 1.2、长轮询 1.3、websocket 1.3.1、websocket原理 1.3.2、django框架 asgi.py在django项目同名app目录下 1.3.3、聊天室 django代码总结 小结 1.3.4、群聊(一) 前端代码 后端代码 1.3.5、群聊(二)…...

学习使用axios,绑定动态数据
目录 axios特性 案例一:通过axios获取笑话 案例二:调用城市天气api接口数据实现天气查询案例 axios特性 支持 Promise API 拦截请求和响应(可以在请求前及响应前做某些操作,例如,在请求前想要在这个请求头中加一些…...

c语言内存函数的深度解析
本章对 memcpy,memmove,memcmp 三个函数进行详解和模拟实现; 本章重点:3个常见内存函数的使用方法及注意事项并学会模拟实现; 如果您觉得文章不错,期待你的一键三连哦,你的鼓励是我创作的动力…...

低代码平台介绍(国内常见的)
文章目录 前言1、阿里云宜搭2、腾讯云微搭3、百度爱速搭4、华为云Astro轻应用 Astro Zero(AppCube)5、字节飞书多维表格6、云程低代码平台7、ClickPaaS8、网易轻舟9、用友YonBuilder10、金蝶苍穹云平台11、泛微平台12、蓝凌低代码平台13、简道云14、轻流…...

matlab RRR机械臂 简略代码
RRR机器人!启动! gazebo在arm mac上似乎难以运行,退而选择Matlab,完成老师第一个作业,现学现卖,权当记录作业过程,有不足之处,多多指教。 作业!启动! RRR机…...

集成测试,单元测试隔离 maven-surefire-plugin
详见 集成测试,单元测试隔离 maven-surefire-plugin maven的goal生命周期 Maven生存周期 - 含 integration-test Maven本身支持的命令(Goals)是有顺序的,越后面执行的命令,会将其前面的命令和其本身按顺序执行一遍,…...

渗透测试基础知识(1)
渗透基础知识一 一、Web架构1、了解Web2、Web技术架构3、Web客户端技术4、Web服务端组成5、动态网站工作过程6、后端存储 二、HTTP协议1、HTTP协议解析2、HTTP协议3、http1.1与http2.0的区别4、HTTP协议 三、HTTP请求1、发起HTTP请求2、HTTP响应与请求-HTTP请求3、HTTP响应与请…...

Android NDK开发
工程目录图 NDK中文官网 请点击下面工程名称,跳转到代码的仓库页面,将工程 下载下来 Demo Code 里有详细的注释 代码:TestNDK 参考文献 Android NDK 从入门到精通(汇总篇)Android JNI(一)——NDK与JNI基础Android之…...

使用python爬取淘宝商品信息
要使用Python爬取淘宝商品信息,您可以按照以下步骤: 安装必要的库 您需要安装Python的requests库和BeautifulSoup库。 要使用Python爬取淘宝商品信息,您可以按照以下步骤:安装必要的库 您需要安装Python的requests库和Beautifu…...

QEMU源码全解析18 —— QOM介绍(7)
接前一篇文章:QEMU源码全解析17 —— QOM介绍(6) 本文内容参考: 《趣谈Linux操作系统》 —— 刘超,极客时间 《QEMU/KVM》源码解析与应用 —— 李强,机械工业出版社 特此致谢! 上一回完成了对…...

【华为OD机试】 选修课
题目描述 现有两门选修课,每门选修课都有一部分学生选修,每个学生都有选修课的成绩,需要你找出同时选修了两门选修课的学生,先按照班级进行划分,班级编号小的先输出,每个班级按照两门选修课成绩和的降序排序…...

225. 用队列实现栈
请你仅使用两个队列实现一个后入先出(LIFO)的栈,并支持普通栈的全部四种操作(push、top、pop 和 empty)。 实现 MyStack 类: void push(int x) 将元素 x 压入栈顶。 int pop() 移除并返回栈顶元素。 int to…...

IDEA将本地项目上传到码云
一、创建本地仓库并关联 用IDEA打开项目,在菜单栏点击vcs->create git repository创建本地仓库, 选择当前项目所在的文件夹当作仓库目录。 二、将项目提交本地仓库 项目名右键就会出现“GIT”这个选项->Add->Commit Directory, 先将项目add…...

Ubuntu更改虚拟机网段(改成桥接模式无法连接网络)
因为工作需要,一开始在安装vmware和虚拟机时,是用的Nat网络。 现在需要修改虚拟机网段,把ip设置成和Windows端同一网段,我们就要去使用桥接模式。 环境: Windows10、Ubuntu20.04虚拟机编辑里打开虚拟网络编辑器&#…...

谷粒商城第七天-商品服务之分类管理下的删除、新增以及修改商品分类
目录 一、总述 1.1 前端思路 1.2 后端思路 二、前端部分 2.1 删除功能 2.2 新增功能 2.3 修改功能 三、后端部分 3.1 删除接口 3.2 新增接口 3.3 修改接口 四、总结 一、总述 1.1 前端思路 删除和新增以及修改的前端无非就是点击按钮,就向后端发送请求…...

Redis学习路线(1)—— Redis的安装
一、NoSQL SQL VS NoSQL 1、名称 SQL 主要是指关系数据库。NoSQL 主要是指非关系数据库。 2、存储结构 SQL 是结构化的数据库,以表格的形式存储数据。NoSQL 是非结构化的数据库,以Key-Value(Redis),JSON格式文档&…...

《MySQL 实战 45 讲》课程学习笔记(五)
数据库锁:全局锁、表锁和行锁 根据加锁的范围,MySQL 里面的锁大致可以分成全局锁、表级锁和行锁三类。 全局锁 全局锁就是对整个数据库实例加锁。 MySQL 提供了一个加全局读锁的方法,命令是 Flush tables with read lock (FTWRL)。当你需要…...

使用GADL对高程数据进行填洼
对于DEM数据中存在的洼地(sink)问题,可以使用GADL(Geospatial Data Abstraction Library)中的功能进行填洼操作。GADL是一个开源的GIS库,提供了许多对地理空间数据进行处理和分析的功能。 下面是使用GADL对…...

Spring Boot集成Swagger3.0,Knife4j导出文档
文章目录 Spring Boot集成Swagger3.0,Knife4j导出文档效果展示如何使用简要说明添加依赖添加配置类测试接口token配置位置 官网 说明情况 demo Spring Boot集成Swagger3.0,Knife4j导出文档 效果展示 如何使用 简要说明 Knife4j的前身是swagger-bootstrap-ui,前身swagger-boo…...

在.NET Framework中的连接字符串ConnectionStrings属性
在.NET Framework中,ConfigurationManager.ConnectionStrings属性是用来访问在Visual Studio IDE应用程序配置文件中配置的数据库连接字符串的。每个连接字符串在Visual Studio IDE配置文件中都以<add>元素的形式出现,该元素是<connectionStrin…...

kafka消费报错卡死:内存溢出OutOfMemoryError: Java heap space
文章目录 现象排查解决 现象 我们信控平台使用Java语言开发,Spring Cloud微服务架构,采用容器化部署,所有服务都部署在docker里面,使用docker-compose进行管理,使用portainer进行监控平台部署客户现场后,一…...

mac卸载与安装指定版本node
一、查看当前node.js版本 node -v 二、卸载当前node.js # 这里是卸载npm的 sudo npm uninstall npm -g# 这里是用来删除node创建的各种文件夹 sudo rm -rf /usr/local/lib/node sudo rm -rf /usr/local/lib/node_modules sudo rm -rf /var/db/receipts/org.nodejs.* sudo rm…...

机器学习深度学习——Dropout
👨🎓作者简介:一位即将上大四,正专攻机器学习的保研er 🌌上期文章:机器学习&&深度学习——权重衰减 📚订阅专栏:机器学习&&深度学习 希望文章对你们有所帮助 Drop…...

Intel和AMD 与 x86,ARM,MIPS有什么区别?
先说amd和intel amd和Intel这俩公司的渊源很深,早期时Intel先是自己搞了个x86架构,然后amd拿到了x86的授权也可以自己做x86了。接着intel向64位过渡的时候自己搞了个ia64(x64架构)但是因为和x86架构不兼容市场反应极差࿰…...

QT编写的串口助手
QT编写的串口助手 提前的知识 创建UI界面工程 找帮助文档 添加串口的宏...

C语言字符串的处理
用惯了Java C#这些语言,C语言中处理字符串还是有些不习惯的,所以这里写一下学习笔记。 C中字符串就是字符数组,是指向字符的指针,并且以空字符 \0 结尾,字符串作为函数的参数传递时一般使用指针类型,使用数…...

Docker 阿里云容器镜像服务
阿里云-容器镜像服务ACR 将本地/服务器docker image(镜像)推送到 阿里云容器镜像服务仓库 1. 在容器镜像服务ACR中创建个人实例 2. 进入个人实例 > 命名空间 创建命名空间 3. 进入个人实例 > 镜像仓库 创建镜像仓库 4. 进入镜像仓库 > 基本信…...