Swift如何绑定数据?RxSwift+MVVM传递产品ID索引越界如何解决
问题修复方案
现有问题汇总
- 数组越界错误:
products数组的实际元素数量和UITableView展示的行数不一致,点击时访问了不存在的下标 - 代码笔误:跳转时变量名写错、ViewModel里仓库实例名和错误回调变量名不匹配
- ID传递时序错误:
DetailViewController的ViewModel初始化早于ID赋值,导致请求时拿到的是默认ID而非传递的正确值 - 强制解包风险:接口返回数据处用
!强制解包,空数据时会直接闪退
具体修改步骤
1. 修复SelectViewController跳转逻辑
先加下标安全校验,修正跳转变量名,保证赋值顺序正确:
extension SelectViewController: UITableViewDelegate { func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { tableView.deselectRow(at: indexPath, animated: true) // 下标安全校验,避免越界 guard indexPath.row < products.count else { return } let detailVC = DetailViewController(productId: products[indexPath.row].id) detailVC.product = products[indexPath.row] // 修正笔误:之前写的detailPassVC改为实际创建的detailVC self.navigationController?.pushViewController(detailVC, animated: true) } }
同时确认产品列表数据请求成功、更新products数组后,必须调用tableView.reloadData()刷新列表,保证UITableView的行数和products.count完全一致。
2. 修复DetailViewController初始化逻辑
新增自定义构造器强制传递产品ID,保证ViewModel初始化时ID已经赋值完成:
class DetailViewController: UIViewController { var product = ProductModel() // 改为必传的隐式解包,不设置默认值 var productId: Int! private let disposeBag = DisposeBag() private let viewModel = DetailViewModel() // 自定义构造器强制传入产品ID init(productId: Int) { self.productId = productId super.init(nibName: nil, bundle: nil) } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func viewDidLoad() { super.viewDidLoad() // 此时productId已经赋值完成,再初始化ViewModel initViewModel() } func initViewModel() { let input = DetailViewModel.Input(loadDetailData: getData.asSignal(onErrorJustReturn: ()), id: productId) let output = viewModel.transform(input: input) output.loadDetail.asObservable() .bind(onNext: { [weak self] data in guard let self = self, let data = data else { return } // 替换强制解包为安全可选绑定 self.infoLabel.text = data.detailDescription self.passView.setData(data) self.secondView.setData(data) self.fareView.setData(data) self.totalLabel.text = totalPrice(data) }).disposed(by: disposeBag) } }
3. 修复DetailViewModel代码笔误
修正变量名不匹配的问题,添加弱引用避免循环引用:
class DetailViewModel: BaseViewModel { private let repo = ProductRepository() private let disposeBag = DisposeBag() struct Input { let loadDetailData: Signal<Void> let id: Int } struct Output { let loadDetail: Driver<DetailModel?> let error: Driver<String?> } func transform(input: Input) -> Output { let loadDetail = PublishSubject<DetailModel?>() let msg = PublishSubject<String?>() input.loadDetailData.asObservable() .flatMap { [weak self] _ in guard let self = self else { return Observable.empty() } // 修正变量名:之前写的productRepo改为实际声明的repo return self.repo.detailProduct(input.id) } .subscribe(onNext: { data in switch data { case let .success(detail): loadDetail.onNext(detail) default: // 修正变量名:之前写的errorMessage改为实际声明的msg msg.onNext("load fail") } }).disposed(by: disposeBag) return Output( loadDetail: loadDetail.asDriver(onErrorJustReturn: nil), error: msg.asDriver(onErrorJustReturn: nil) ) } }
额外注意事项
- 所有异步回调闭包都要加
[weak self]避免循环引用导致内存泄漏 - 不要对网络返回的可选值做强制解包,全部用
guard let/if let做安全校验 - 如果产品列表有分页加载、删除等操作,每次更新
products数组后都必须刷新列表,保证数据源和UI同步
内容的提问来源于stack exchange,提问作者earth
相关产品推荐
相关产品推荐

