ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

RxSwift 为什么值得用:以声明式响应式编程驯服异步与状态难题

RxSwift 为什么值得用:以声明式响应式编程驯服异步与状态难题 后端【免费下载链接】RxSwiftReactive Programming in Swift项目地址https://gitcode.com/gh_mirrors/rx/RxSwift点击查看免费下载RxSwift 的核心主张可以浓缩为一句话Rx 让你以声明式declarative的方式构建应用。与其在回调、代理、KVO、通知和一堆瞬时状态变量之间来回切换不如把数据流本身建模为可组合、可复用、可测试的观察序列用少量算子把复杂的异步逻辑拼出来。本文以 Documentation/Why.md 为主体结合 RxSwift 与 RxCocoa 的真实源码逐一拆解绑定、重试、代理、KVO、通知、瞬时状态清理、组合式资源释放、网络请求聚合等经典场景读完你不仅能看懂这些 API 的用法还能从实现层面理解它们为什么可行。为什么是 RxSwift一次从命令式到声明式的范式切换传统命令式编程在处理异步事件时往往需要把未来会发生的事拆散成回调、状态标志和清理代码分散在代码库各处。RxSwift 换了一种思路把一切异步与事件源统一建模为Observable序列事件要么发出元素.next要么终止.error/.completed而算子operators负责对这些序列做变换、过滤、合并与调度。这种抽象让数据怎么流动和数据是什么分离开来代码更贴近业务意图本身。如果你想从设计动机上深入理解这一选择仓库中的 Documentation/DesignRationale.md 和 Documentation/MathBehindRx.md 分别讨论了 Rx 的工程设计哲学与其背后的数学基础算子之间的代数关系例如结合律、分配律它们共同解释了为什么所有 Rx 算子都应该是直观的。下面我们从最直观的场景开始。绑定BindingsUI 数据流的一行式表达UI 编程中大量工作是把数据搬到界面上。用 RxSwift一个问候语标签的绑定可以写成Observable.combineLatest(firstName.rx.text, lastName.rx.text) { $0 $1 } .map { Greetings, \($0) } .bind(to: greetingLabel.rx.text)这段代码没有中间变量、没有textFieldDidChange回调、没有手动label.text ...赋值。combineLatest会在任意一个输入源产生新值时把两个输入的最新值同时交给闭包map负责转换bind(to:)把结果写进标签。从源码看combineLatest的二元实现位于 RxSwift/Observables/CombineLatestarity.swift该文件由仓库 Preprocessor 自动生成支持 2 到 8 个源它内部通过CombineLatestSink2_为每个源维护一个latestElement槽位只有当两个槽位都被填充后才会触发结果选择器并且对每个源都持有独立的SingleAssignmentDisposable订阅保证任一源出错或结束时整体链路随之终止。而bind(to:)的实现位于 RxCocoa/Common/ObservableBind.swift其本质等价于subscribe只是更精确地传达了我要把值绑定到某个目标的意图并让错误在 Debug 模式下直接触发fatalError避免绑定错误被静默吞掉。这套写法对UITableView/UICollectionView同样成立——RxCocoa在 RxCocoa/iOS/UITableViewRx.swift 与 RxCocoa/iOS/UICollectionViewRx.swift 中提供了rx.items(cellIdentifier:cellType:)这类绑定入口viewModel .rows .bind(to: resultsTableView.rx.items(cellIdentifier: WikipediaSearchCell, cellType: WikipediaSearchCell.self)) { (_, viewModel, cell) in cell.title viewModel.title cell.url viewModel.url } .disposed(by: disposeBag)官方建议是即使是简单绑定也始终使用.disposed(by: disposeBag)。DisposeBag实现在 RxSwift/Disposables/DisposeBag.swift会在自身被释放时统一取消其中所有订阅把生命周期管理交给 ARC而不是靠程序员记忆。重试Retries把重试从业务代码中抽离API 总会失败。假设有一个会抛错的方法func doSomethingIncredible(forWho: String) throws - IncredibleThing用命令式方式实现重试你需要额外的计数变量、循环或递归、以及一堆临时状态如果要支持指数退避exponential backoff代码会更复杂而且不可复用。理想的做法是把重试这件事本身抽象出来应用到任意操作上。RxSwift 里一次简单的重试只有一行doSomethingIncredible(me) .retry(3)其实现位于 RxSwift/Observables/Catch.swiftretry(_ maxAttemptCount:)通过CatchSequence(sources: Swift.repeatElement(asObservable(), count: maxAttemptCount))把同一个源序列重复拼接——一旦前一次以.error结束就无缝切换到下一次尝试直到成功或次数耗尽。注意这里的语义是总共最多尝试maxAttemptCount次含第一次。如果retry(3)满足不了需求还可以用retry(when:)见 RxSwift/Observables/RetryWhen.swift自定义重试策略它接收一个通知处理器该处理器拿到一个错误序列返回一个决定何时继续、何时放弃的触发序列——指数退避、最大重试时长、只重试特定错误类型等策略都可以在闭包里自由实现。文档原话是You can also easily create custom retry operators.这正是组合式设计的价值策略与具体业务解耦。代理Delegates从回调矩阵到可组合的数据流传统代理写法的问题是不表达一个scrollViewDidScroll回调里塞满了对这是哪个 scrollView的隐式依赖public func scrollViewDidScroll(scrollView: UIScrollView) { [weak self] // what scroll view is this bound to? self?.leftPositionConstraint.constant scrollView.contentOffset.x }换成 RxSwift同样逻辑变成一条干净的数据流self.resultsTableView .rx.contentOffset .map { $0.x } .bind(to: self.leftPositionConstraint.rx.constant)RxCocoa在 RxCocoa/iOS/UIScrollViewRx.swift 中把contentOffset暴露为ControlPropertyCGPoint后续的map与bind全部是纯数据变换。代理模式被封装成可观察的属性你不再需要关心回调何时被调用、weak self要不要写——bind链路本身就约束了数据去向。类似的封装遍布 RxCocoaUIControl的rx.controlEvent、UIButton的rx.tap、UIBarButtonItem的rx.tap等等。KVO告别内存泄漏与observeValueForKeyPath样板手写 KVO 有两个著名痛点一是忘记移除观察者会导致类似TickTock was deallocated while key value observers were still registered with it...的泄漏警告二是必须实现observeValueForKeyPath:ofObject:change:context:这个又长又难维护的 Objective-C 回调。RxSwift 提供了两个替代入口实现在 RxCocoa/Foundation/NSObjectRx.swiftview.rx.observe(CGRect.self, frame) .subscribe(onNext: { frame in print(Got new frame \(frame)) }) .disposed(by: disposeBag)someSuspiciousViewController .rx.observeWeakly(Bool.self, behavingOk) .subscribe(onNext: { behavingOk in print(Cats can purr? \(behavingOk)) }) .disposed(by: disposeBag)rx.observe与rx.observeWeakly的差异值得注意见 RxCocoa/Foundation/NSObjectRx.swift 与 RxCocoa/Foundation/NSObjectRx.swiftobserve默认options [.new, .initial]会立即发出当前值且对目标对象持有强引用observeWeakly使用弱引用并且内部对 keyPath 做了按.分段的层级解析见同文件的observeWeaklyKeyPathFor适合观察随时可能被释放的对象如页面控制器。两者返回的都是Observable因此可以继续.filter、.map、.distinctUntilChanged而不是在回调里手写一堆 if。更完整的 KVO 用法与注意事项见 Documentation/GettingStarted.md。通知Notifications把addObserverForName包装成可组合序列系统通知的传统用法是addObserverForName:object:queue:usingBlock:返回一个NSObjectProtocoltoken需要手动removeObserver否则同样有泄漏风险。RxSwift 的替代写法NotificationCenter.default .rx.notification(NSNotification.Name.UITextViewTextDidBeginEditing, object: myTextView) .map { /*do something with data*/ } ....其实现位于 RxCocoa/Foundation/NotificationCenterRx.swift内部通过Observable.create注册系统 observer并在返回的Disposables.create闭包里调用removeObserver——取消订阅即自动移除通知观察者。也就是说你把注册/注销的配对责任交给了DisposeBag彻底消灭了忘记 removeObserver这一类 bug。瞬时状态Transient State自动补全搜索框的完整解异步编程里最折磨人的是瞬时状态请求在途时又来了新输入要不要取消旧请求失败了要不要重试、重试几次要不要防抖加载中/失败后界面显示什么用命令式写法每个问题都对应一个额外字段写完还要逐个测试。以自动补全搜索框为例当用户输入abc中的c时为ab发起的请求应该被取消失败要做重试输入过程中不应每个字符都打服务器需要节流执行期间和全部重试都失败后界面上分别要展示什么。这些在 RxSwift 里是同一段流水线searchTextField.rx.text .throttle(.milliseconds(300), scheduler: MainScheduler.instance) .distinctUntilChanged() .flatMapLatest { query in API.getSearchResults(query) .retry(3) .startWith([]) // clears results on new search term .catchErrorJustReturn([]) } .subscribe(onNext: { results in // bind to ui }) .disposed(by: disposeBag)不需要任何额外的标志位或字段——Rx 负责收拾所有瞬时状态。逐个看这条链路背后的实现throttleRxSwift/Observables/Throttle.swift保证任意两次发射间隔不小于dueTime默认latest: true即窗口内到来的最新元素会在窗口结束时补发配合MainScheduler.instance在主线程计时天然实现停止输入 300ms 后才发请求distinctUntilChangedRxSwift/Observables/DistinctUntilChanged.swift去掉与上一个值重复的输入flatMapLatestRxSwift/Observables/Switch.swift本质是mapswitchLatest——每个新输入都会取消上一个内部订阅于是为ab的旧请求被取消成为默认行为startWith([])新搜索词到来时立即先发一个空数组清空旧结果catchErrorJustReturn([])即使全部重试失败也把错误转换为空结果让界面始终有确定的状态。组合式资源释放Compositional Disposal按需取消昂贵的异步操作设想这样一个场景表格里的每个 cell 需要先从 URL 拉图、再解码、再模糊处理。这三个需求很自然cell 滑出可视区时应取消整条链路带宽和模糊计算的 CPU 都昂贵cell 刚进入可视区时不要立刻拉图快速滑动会制造大量发了又取消的请求同时应限制并发图片操作数。这段概念性代码用 Rx 一次解决// this is a conceptual solution let imageSubscription imageURLs .throttle(.milliseconds(200), scheduler: MainScheduler.instance) .flatMapLatest { imageURL in API.fetchImage(imageURL) } .observeOn(operationScheduler) .map { imageData in return decodeAndBlurImage(imageData) } .observeOn(MainScheduler.instance) .subscribe(onNext: { blurredImage in imageView.image blurredImage }) .disposed(by: reuseDisposeBag)关键在于释放是组合式的当imageSubscription被 dispose这里通过复用 cell 时替换的reuseDisposeBag时整条链路上所有依赖的异步操作都会被级联取消绝不会有野图被绑定到 UI 上。这背后的机制是每个算子都实现Disposablesubscribe返回的Disposable内部组合了所有子订阅RxSwift/Disposables/Disposables.swift 提供了Disposables.create(...)这类工厂方法来组装多个清理动作observeOnRxSwift/Observables/ObserveOn.swift负责切换后续回调执行的调度器图片的拉取、解码、模糊放在后台operationScheduler而最终更新 UI 切回MainScheduler.instance避免在主线程做昂贵计算、也避免在后台线程碰 UIKit。聚合网络请求Aggregating Network Requestszip与线程切换当需要同时发起两个请求、并在两者都完成后聚合结果时用ziplet userRequest: ObservableUser API.getUser(me) let friendsRequest: Observable[Friend] API.getFriends(me) Observable.zip(userRequest, friendsRequest) { user, friends in return (user, friends) } .subscribe(onNext: { user, friends in // bind them to the user interface }) .disposed(by: disposeBag)zip的多元实现位于 RxSwift/Observables/Ziparity.swift同样由 Preprocessor 生成支持 2 到 8 个源它会为每个源维护队列只有当每个队列都至少有一个元素时才成对取出并通过结果选择器合并天然满足都完成才聚合的语义。如果这两个 API 在后台线程返回结果、而绑定必须发生在主线程加上observeOn即可Observable.zip(userRequest, friendsRequest) { user, friends in return (user, friends) } .observeOn(MainScheduler.instance) .subscribe(onNext: { user, friends in // bind them to the user interface }) .disposed(by: disposeBag)MainSchedulerRxSwift/Schedulers/MainScheduler.swift保证后续回调在主线程执行UI 更新的线程安全由框架接管。状态State在函数式与命令式之间取平衡可变语言让读全局状态、改全局状态变得太容易而对共享可变状态的无序修改极易引发组合爆炸combinatorial explosion。反过来纯函数式风格虽然更安全但可能牺牲贴近硬件的效率。社区通常的应对方式是让状态尽量简单并用单向数据流unidirectional data flow建模派生数据——这正是 Rx 的主场它处在函数式与命令式世界的交汇点允许你用不可变定义和纯函数去处理可变状态的快照以可靠、可组合的方式消费它们。这一设计理念也直接体现在 RxSwift 的类型体系上ObservableTypeRxSwift/ObservableType.swift与ObserverTypeRxSwift/ObserverType.swift把事件流抽象成协议EventRxSwift/Event.swift只有.next/.error/.completed三种形态整个数据管道因此是可推导、可测试的。轻松集成Easy Integration用Observable.create自定义 Observable如果你需要把既有代码闭包回调、URLSession、CLLocationManager等包装成 ObservableObservable.create是最直接的入口。RxCocoa 包装 HTTP 请求的完整代码节选自 RxCocoa/Foundation/URLSessionRx.swift 的思路以response(request:)为代表如下extension Reactive where Base: URLSession { public func response(request: URLRequest) - Observable(Data, HTTPURLResponse) { return Observable.create { observer in let task self.base.dataTask(with: request) { (data, response, error) in guard let response response, let data data else { observer.on(.error(error ?? RxCocoaURLError.unknown)) return } guard let httpResponse response as? HTTPURLResponse else { observer.on(.error(RxCocoaURLError.nonHTTPResponse(response: response))) return } observer.on(.next(data, httpResponse)) observer.on(.completed) } task.resume() return Disposables.create(with: task.cancel) } } }这段代码展示了一个关键模式订阅发生时才真正发起请求task.resume()在create的闭包里并且返回的 Disposable 会调用task.cancel——一旦订阅被取消底层网络任务立即终止。错误路径通过RxCocoaURLError定义在 RxCocoa/Foundation/URLSessionRx.swift包含unknown、nonHTTPResponse、httpRequestFailed、deserializationError等 case统一表达让下游可以按错误类型做差异化处理。Observable.create的实现位于 RxSwift/Observables/Create.swift它生成一个AnonymousObservable其 sinkAnonymousObservableSink用AtomicInt状态保证序列终止后.error/.completed不再转发任何元素同时提供了 Debug 模式下的同步追踪SynchronizationTracker用于在开发期揪出多个线程同时向同一个 observer 发事件的违规。收益总结Benefits把上面这些场景合起来文档对用 Rx 之后代码会变成什么样做了一个精炼总结Composable可组合——组合就是 Rx 的代名词Reusable可复用——因为可组合一段管道可以到处复用Declarative声明式——定义不可变只有数据在流动Understandable and concise易懂且简洁——提高抽象层次、消除瞬时状态Stable稳定——Rx 核心代码经过大量单元测试验证仓库 Tests/RxSwiftTests 与 Sources/AllTestz 下的测试用例覆盖了几乎所有算子的行为例如 ObservableThrottleTests.swift、ObservableCombineLatestTests.swiftLess stateful更少状态——把应用建模为单向数据流Without leaks无泄漏——资源管理DisposeBag、Disposable简单可靠。不是全有或全无Its Not All or Nothing用 Rx 建模应用通常多多益善但如果担心我还不会那么多算子文档给出了两个安心的结论常用算子其实很少——大约 10 到 15 个算子就能覆盖绝大多数典型场景其中就包括你熟悉的map、filter、zip、observeOn等。所有 Rx 算子都建立在数学基础之上详见 Documentation/MathBehindRx.md行为应当直觉可猜每个算子还有对应的弹珠图marble diagram帮助理解时序行为边界场景可以随时进出——如果需要某个不存在的算子你可以自定义算子如果某段遗留代码实在不适合响应式改造你也可以从 Rx 的单子里跳出来处理完数据再回去。具体做法见 Documentation/GettingStarted.md 中Life happens一节。换句话说RxSwift 不是要求你一步到位重构整个 App而是给你一把可以逐步扩大使用范围、随时进出、且底层行为可被源码验证的瑞士军刀。理解了本文的绑定、重试、KVO、通知、瞬时状态清理、组合式释放、请求聚合与自定义 Observable 这八类模式你就掌握了 RxSwift 日常实战中最核心的思维框架。赞分享后端【免费下载链接】RxSwiftReactive Programming in Swift项目地址https://gitcode.com/gh_mirrors/rx/RxSwift点击查看免费下载相关推荐像素到特征Maths, CS AI Compendium图像基础全解含SIFT特征提取像素到特征Maths, CS AI Compendium图像基础全解含SIFT特征提取 想用直觉理解 图像基础 吗开源教材《Maths, CS 文档教程知识库免费音乐歌词提取终极指南如何轻松获取网易云QQ音乐LRC歌词免费音乐歌词提取终极指南如何轻松获取网易云QQ音乐LRC歌词 还在为找不到心爱歌曲的歌词而烦恼吗 今天我要为你介绍一款超实用的 免费歌词下载工具 ——1桌面应用音视频终极指南如何用OpenCore Legacy Patcher让老Mac重获新生终极指南如何用OpenCore Legacy Patcher让老Mac重获新生 还在为老款Mac无法升级到最新macOS系统而烦恼吗OpenCore Leg操作系统固件驱动开发上一篇Mistral-7B-Instruct-v0.1-openmind安全指南构建可靠的AI对话系统下一篇【亲测免费】 **深入探索MeshLab三维几何模型处理的利器**创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表