iOS:Combine实现View通知VC压入导航控制器失败问题解决
Combine替代Delegate实现View到VC事件通知的问题排查与解决
核心排查点
1. 订阅生命周期是重灾区
如果VC里订阅了View的发布者,但没把AnyCancellable存起来,订阅会瞬间销毁,根本收不到事件。必须把订阅对象存在VC的Set<AnyCancellable>或者单独的属性里,不能只写临时变量。
2. 检查View里的事件发送逻辑
看看TUCRepositoryListView里点击cell文本、头像的时候,是不是真的调用了PassthroughSubject的send(_:)方法。另外最好用枚举把事件类型封装起来(比如区分仓库点击和用户点击),别因为类型不匹配导致事件发了但收不到。
3. 确认VC的订阅时机和线程
订阅得等View初始化完成、加到视图层级之后再搞,别View还没创建就订阅。还有UI操作必须在主线程,订阅的时候记得加receive(on: DispatchQueue.main),不然可能触发不了导航跳转。
4. 内存管理别踩坑
订阅sink的时候一定要用[weak self]捕获self,不然容易循环引用导致VC释放不了,同时也能避免因为强引用导致的事件异常。
修正后的代码示例
第一步:给View定义事件发布者
import UIKit import Combine // 用枚举把View能发出的事件明确区分开 enum TUCRepositoryListViewEvent { case didTapRepo(repo: Repository) case didTapUser(user: User) } class TUCRepositoryListView: UIView { // 用来发送事件的PassthroughSubject let eventSubject = PassthroughSubject<TUCRepositoryListViewEvent, Never>() private var cancellables = Set<AnyCancellable>() override init(frame: CGRect) { super.init(frame: frame) setupTableView() } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } private func setupTableView() { // 这里放你的tableView配置代码 tableView.delegate = self tableView.dataSource = self } } // 处理tableView点击和自定义cell的头像点击,转发到Combine发布者 extension TUCRepositoryListView: UITableViewDelegate { func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { guard let repo = dataSource?[indexPath.row] else { return } eventSubject.send(.didTapRepo(repo: repo)) } // 假设这是自定义cell头像点击的回调方法 func onUserAvatarTapped(user: User) { eventSubject.send(.didTapUser(user: user)) } }
第二步:VC里订阅事件并处理导航
import UIKit import Combine class TUCSearchRepoListViewController: UIViewController { private let repoListView = TUCRepositoryListView() private var cancellables = Set<AnyCancellable>() override func viewDidLoad() { super.viewDidLoad() setupSubviews() bindViewEvents() } private func setupSubviews() { view.addSubview(repoListView) // 这里加你的布局约束 } private func bindViewEvents() { repoListView.eventSubject .receive(on: DispatchQueue.main) // 强制在主线程处理UI操作 .sink(receiveValue: { [weak self] event in guard let self = self else { return } switch event { case .didTapRepo(let repo): let detailVC = TUCRepositoryDetailsViewController(repo: repo) self.navigationController?.pushViewController(detailVC, animated: true) case .didTapUser(let user): let userVC = TUCUserDetailsViewController(user: user) self.navigationController?.pushViewController(userVC, animated: true) } }) .store(in: &cancellables) // 把订阅存起来,保证生命周期 } }
额外提醒
- 如果自定义cell内部也用Combine发事件,要确保cell的发布者被正确订阅到View的
eventSubject,别让事件半路丢失。 - 排查的时候可以在
sink里加个print(event),看看有没有收到事件,就能快速定位是发送端还是接收端的问题。 - 别在View的
init里直接发事件,那时候VC还没完成订阅,肯定收不到。
内容的提问来源于stack exchange,提问作者Filip Varda
相关产品推荐
相关产品推荐

