You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.26 09:30:16