iOS RxSwift:基于Observable数组创建事件视图,嵌套订阅是否合理?
基于Observable数组实现自定义视图订阅的正确方式
你的当前实现存在的问题
你的代码采用了订阅嵌套订阅的写法,这会带来几个关键问题:
- 重复订阅与事件重复触发:每次
doItems数组更新时,你都会给已存在的视图按钮重新添加订阅。这意味着同一个按钮的点击事件会被多个订阅监听,导致回调逻辑多次执行。 - 内存泄漏风险:旧视图的订阅会一直保留在父控制器的
disposeBag中,即使视图被销毁或复用,这些订阅仍可能持有视图的引用,引发内存泄漏。 - 代码可维护性差:嵌套订阅让代码结构混乱,逻辑分散,后续修改或扩展会变得困难。
优化方案:封装视图事件 + 合理管理订阅生命周期
正确的做法是将自定义视图的事件逻辑封装在视图内部,并通过暴露Observable的方式让外部订阅,同时严格管理订阅的生命周期。
步骤1:重构自定义视图(DoItemView)
让视图自身管理UI事件的Observable,避免在外部嵌套订阅:
class DoItemView: UIView { // 数据模型 private var doItem: DoItem! // UI组件 let checkButton = UIButton() let deleteButton = UIButton() let editButton = UIButton() // 暴露给外部的事件Observable var onCheckTap: Observable<DoItem> { checkButton.rx.tap.map { self.doItem } } var onDeleteTap: Observable<DoItem> { deleteButton.rx.tap.map { self.doItem } } var onEditTap: Observable<DoItem> { editButton.rx.tap.map { self.doItem } } var onViewTap: Observable<DoItem> { rx.tapGesture() .when(.recognized) .map { self.doItem } } override init(frame: CGRect) { super.init(frame: frame) setupUI() } required init?(coder: NSCoder) { super.init(coder: coder) setupUI() } // 绑定数据到视图 func bind(with item: DoItem) { self.doItem = item // 根据数据更新UI状态,例如: // checkButton.isSelected = item.isChecked } private func setupUI() { // 配置按钮布局、样式等UI初始化逻辑 } }
步骤2:在父控制器中优化订阅逻辑
使用独立的DisposeBag管理视图事件的订阅,每次数组更新时重置该DisposeBag,避免重复订阅:
class YourViewController: UIViewController { private let disposeBag = DisposeBag() // 专门用于管理视图事件的订阅,每次数组更新时重置 private var viewEventsDisposeBag = DisposeBag() override func viewDidLoad() { super.viewDidLoad() bindDoItems() } private func bindDoItems() { doTableViewModel.doItems .observe(on: MainScheduler.instance) .subscribe(onNext: { [weak self] viewModelItems in guard let self = self else { return } // 重置视图事件订阅,清理旧订阅 self.viewEventsDisposeBag = DisposeBag() // 更新视图数据 self.doTableView.reloadData(viewModelItems: viewModelItems) // 收集所有视图的事件Observable let checkTaps = self.doTableView.doItemViews.map { $0.onCheckTap } let deleteTaps = self.doTableView.doItemViews.map { $0.onDeleteTap } let editTaps = self.doTableView.doItemViews.map { $0.onEditTap } let viewTaps = self.doTableView.doItemViews.map { $0.onViewTap } // 订阅视图点击事件 Observable.merge(viewTaps) .subscribe(onNext: { item in print(item.doID) }) .disposed(by: self.viewEventsDisposeBag) // 订阅勾选按钮事件 Observable.merge(checkTaps) .subscribe(onNext: { item in self.tappedDoItemCheck(viewDoItem: item) }) .disposed(by: self.viewEventsDisposeBag) // 订阅删除按钮事件 Observable.merge(deleteTaps) .subscribe(onNext: { item in self.tappedDoItemRemove(doItem: item) }) .disposed(by: self.viewEventsDisposeBag) // 订阅编辑按钮事件 Observable.merge(editTaps) .subscribe(onNext: { item in self.tappedDoItemEdit(doItem: item) }) .disposed(by: self.viewEventsDisposeBag) self.doTableView.configureLayer() }) .disposed(by: disposeBag) } // 你的回调方法 private func tappedDoItemCheck(viewDoItem: DoItem) { /* ... */ } private func tappedDoItemRemove(doItem: DoItem) { /* ... */ } private func tappedDoItemEdit(doItem: DoItem) { /* ... */ } }
额外建议
如果你的自定义doTableView是基于UITableView或UICollectionView实现的,推荐使用RxDataSource库,它是RxSwift官方提供的数据源绑定工具,能更优雅地处理数组数据与视图的绑定,自动管理单元格的复用和事件订阅,进一步简化代码。
内容的提问来源于stack exchange,提问作者Cook Kim
相关产品推荐
相关产品推荐

