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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.10 22:15:29