MVVM+RxSwift实践疑问:如何链式分组方法实现加载流程?
嘿,很高兴看到你在探索 MVVM+RxSwift 的实践!你的这个链式分组组织逻辑的思路完全合理,而且非常贴合 RxSwift 的响应式设计风格,甚至可以进一步优化得更优雅、更符合 MVVM 的职责分离原则。
先给你吃颗定心丸:你的思路是可行的
你提到的 doFirst(loading=true)(对应 RxSwift 里的 do(onSubscribe:))、doNext(getData)、doLast(loading=false)(对应 do(onDispose:) 或 do(onCompleted:)/do(onError:))加上错误捕获的链式写法,是 Rx 中管理「副作用」(比如加载状态、日志)的标准玩法,完全可以用来组织请求逻辑后再订阅。
优化后的实践方案(贴合 MVVM)
我们可以把请求逻辑、状态管理完全封装在 ViewModel 中,ViewController 只负责 UI 绑定,这样代码更清晰,也符合 MVVM 的核心思想。
1. ViewModel 层:封装请求与状态
import RxSwift import RxCocoa class UsersViewModel { private let apiService: APIService private let disposeBag = DisposeBag() // 响应式加载状态(供VC绑定UI) let isLoading = BehaviorRelay<Bool>(value: false) // 用户数据序列(供VC订阅刷新UI) let users = PublishSubject<[User]>() // 错误事件序列(供VC处理错误提示) let apiError = PublishSubject<Error>() init(apiService: APIService) { self.apiService = apiService } // 封装请求逻辑,用链式处理副作用 func fetchUsers() { apiService.getUsers() // 请求开始时启动加载 .do(onSubscribe: { [weak self] in self?.isLoading.accept(true) }) // 请求结束(成功/失败)时停止加载 .do(onDispose: { [weak self] in self?.isLoading.accept(false) }) // 订阅处理数据和错误 .subscribe( onNext: { [weak self] fetchedUsers in self?.users.onNext(fetchedUsers) }, onError: { [weak self] error in self?.apiError.onNext(error) } ) .disposed(by: disposeBag) } }
2. ViewController 层:只做 UI 绑定
import UIKit import RxSwift import RxCocoa class ViewController: UIViewController { private let apiService = APIService() private lateinit var viewModel: UsersViewModel private let disposeBag = DisposeBag() // 假设你有这些UI控件 @IBOutlet weak var activityIndicator: UIActivityIndicatorView! @IBOutlet weak var usersTableView: UITableView! override func viewDidLoad() { super.viewDidLoad() setupViewModel() bindViewModelToUI() // 触发数据请求 viewModel.fetchUsers() } private func setupViewModel() { viewModel = UsersViewModel(apiService: apiService) } private func bindViewModelToUI() { // 绑定加载状态到指示器 viewModel.isLoading .bind(to: activityIndicator.rx.isAnimating) .disposed(by: disposeBag) // 绑定用户数据到表格 viewModel.users .bind(to: usersTableView.rx.items(cellIdentifier: "UserCell", cellType: UserCell.self)) { index, user, cell in cell.configure(with: user) } .disposed(by: disposeBag) // 绑定错误事件到提示弹窗 viewModel.apiError .subscribe(onNext: { [weak self] error in self?.showErrorAlert(message: error.localizedDescription) }) .disposed(by: disposeBag) } private func showErrorAlert(message: String) { let alert = UIAlertController(title: "请求失败", message: message, preferredStyle: .alert) alert.addAction(UIAlertAction(title: "确定", style: .default)) present(alert, animated: true) } }
额外优化点:让链式更纯粹
如果你更偏好直接返回可观察序列让 VC 订阅,可以把 ViewModel 的方法改成这样:
func fetchUsers() -> Observable<[User]> { return apiService.getUsers() .do(onSubscribe: { [weak self] in self?.isLoading.accept(true) }) .do(onDispose: { [weak self] in self?.isLoading.accept(false) }) .catch { [weak self] error in self?.apiError.onNext(error) return Observable.empty() // 或返回默认空数组,根据业务需求调整 } }
然后在 VC 里直接订阅这个序列:
viewModel.fetchUsers() .bind(to: usersTableView.rx.items(...)) .disposed(by: disposeBag)
关键注意事项
- 避免循环引用:所有闭包内都要用
[weak self],防止 ViewModel/VC 因为 Rx 订阅持有而内存泄漏。 - 状态选择合适的 Relay/Subject:
BehaviorRelay适合有初始值的状态(比如加载状态初始为 false),PublishSubject适合一次性事件(比如数据返回、错误提示)。 - 错误处理灵活调整:如果希望错误后序列不终止,可以用
catchErrorJustReturn返回默认值,或者catchError切换到备用请求序列。
内容的提问来源于stack exchange,提问作者Dex Sebas
相关产品推荐
相关产品推荐

