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)。当你需要…...
mongodb源码分析session执行handleRequest命令find过程
mongo/transport/service_state_machine.cpp已经分析startSession创建ASIOSession过程,并且验证connection是否超过限制ASIOSession和connection是循环接受客户端命令,把数据流转换成Message,状态转变流程是:State::Created 》 St…...

376. Wiggle Subsequence
376. Wiggle Subsequence 代码 class Solution { public:int wiggleMaxLength(vector<int>& nums) {int n nums.size();int res 1;int prediff 0;int curdiff 0;for(int i 0;i < n-1;i){curdiff nums[i1] - nums[i];if( (prediff > 0 && curdif…...

k8s业务程序联调工具-KtConnect
概述 原理 工具作用是建立了一个从本地到集群的单向VPN,根据VPN原理,打通两个内网必然需要借助一个公共中继节点,ktconnect工具巧妙的利用k8s原生的portforward能力,简化了建立连接的过程,apiserver间接起到了中继节…...
【HarmonyOS 5 开发速记】如何获取用户信息(头像/昵称/手机号)
1.获取 authorizationCode: 2.利用 authorizationCode 获取 accessToken:文档中心 3.获取手机:文档中心 4.获取昵称头像:文档中心 首先创建 request 若要获取手机号,scope必填 phone,permissions 必填 …...
今日学习:Spring线程池|并发修改异常|链路丢失|登录续期|VIP过期策略|数值类缓存
文章目录 优雅版线程池ThreadPoolTaskExecutor和ThreadPoolTaskExecutor的装饰器并发修改异常并发修改异常简介实现机制设计原因及意义 使用线程池造成的链路丢失问题线程池导致的链路丢失问题发生原因 常见解决方法更好的解决方法设计精妙之处 登录续期登录续期常见实现方式特…...

接口自动化测试:HttpRunner基础
相关文档 HttpRunner V3.x中文文档 HttpRunner 用户指南 使用HttpRunner 3.x实现接口自动化测试 HttpRunner介绍 HttpRunner 是一个开源的 API 测试工具,支持 HTTP(S)/HTTP2/WebSocket/RPC 等网络协议,涵盖接口测试、性能测试、数字体验监测等测试类型…...

基于PHP的连锁酒店管理系统
有需要请加文章底部Q哦 可远程调试 基于PHP的连锁酒店管理系统 一 介绍 连锁酒店管理系统基于原生PHP开发,数据库mysql,前端bootstrap。系统角色分为用户和管理员。 技术栈 phpmysqlbootstrapphpstudyvscode 二 功能 用户 1 注册/登录/注销 2 个人中…...

第一篇:Liunx环境下搭建PaddlePaddle 3.0基础环境(Liunx Centos8.5安装Python3.10+pip3.10)
第一篇:Liunx环境下搭建PaddlePaddle 3.0基础环境(Liunx Centos8.5安装Python3.10pip3.10) 一:前言二:安装编译依赖二:安装Python3.10三:安装PIP3.10四:安装Paddlepaddle基础框架4.1…...

Linux 下 DMA 内存映射浅析
序 系统 I/O 设备驱动程序通常调用其特定子系统的接口为 DMA 分配内存,但最终会调到 DMA 子系统的dma_alloc_coherent()/dma_alloc_attrs() 等接口。 关于 dma_alloc_coherent 接口详细的代码讲解、调用流程,可以参考这篇文章,我觉得写的非常…...
13.10 LangGraph多轮对话系统实战:Ollama私有部署+情感识别优化全解析
LangGraph多轮对话系统实战:Ollama私有部署+情感识别优化全解析 LanguageMentor 对话式训练系统架构与实现 关键词:多轮对话系统设计、场景化提示工程、情感识别优化、LangGraph 状态管理、Ollama 私有化部署 1. 对话训练系统技术架构 采用四层架构实现高扩展性的对话训练…...