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

Swift Weak Delegate始终为nil求助:Coordinator回调失败

Delegate Always Nil When Using Coordinator Pattern in Swift

Problem Description

I'm working on an iOS app using the Coordinator pattern, and I've run into an issue: when the userPressed(at:) method in ListViewModel is called, I want to notify the Coordinator via a delegate callback to launch another Coordinator, but the delegate property is always nil. I already used the weak modifier on the delegate, but it's still not working. Here's my relevant code:

public func start() { 
    let storyboard = UIStoryboard(name: "Main", bundle: Bundle.main) 
    guard let listVC = storyboard.instantiateViewController(withIdentifier: "ListVC") as? ListVC else { 
        return 
    } 
    let viewModel = ListViewModel(dependencies: appDependencies) 
    viewModel.delegate = self 
    listVC.listViewModel = viewModel 
    navigationController?.pushViewController(listVC, animated: true) 
} 

protocol ListViewModelDelegate: class { 
    func needChangeScreen(cellViewModel: UserCellViewModel) 
} 

final class ListViewModel { 
    weak var delegate: ListViewModelDelegate? 
    func userPressed(at index: IndexPath) { 
        delegate?.needChangeScreen(cellViewModel: cellViewModels[index.row]) 
    } 
}

Solutions & Troubleshooting Steps

Let's break down the most common reasons for this issue and how to fix them:

1. Verify Your Coordinator Conforms to the Delegate Protocol

First, double-check that your Coordinator class properly declares conformance to ListViewModelDelegate and implements the required method. If you skip this step, the compiler might not throw an error (thanks to Swift's type inference), but the delegate reference will end up nil at runtime.

Example of a correctly conforming Coordinator:

final class ListCoordinator: NSObject, ListViewModelDelegate {
    private let navigationController: UINavigationController
    private let appDependencies: AppDependencies // Match your dependency type

    init(navigationController: UINavigationController, dependencies: AppDependencies) {
        self.navigationController = navigationController
        self.appDependencies = dependencies
        super.init()
    }

    public func start() {
        // Your existing start() code here
    }

    // Implement the delegate method
    func needChangeScreen(cellViewModel: UserCellViewModel) {
        // Add logic here to launch your next Coordinator
        let detailCoordinator = DetailCoordinator(navigationController: navigationController, cellViewModel: cellViewModel)
        detailCoordinator.start()
    }
}

2. Check the ViewModel's Lifecycle

Ensure your ListViewModel instance isn't being deallocated prematurely:

  • Confirm the listViewModel property in ListVC uses a strong reference (the default in Swift, no weak modifier). The ViewController should hold onto the ViewModel, while the Coordinator holds a weak reference as the delegate—this avoids retain cycles.
  • Make sure you aren't reinitializing ListViewModel elsewhere in ListVC. If you create a new instance later in the VC's lifecycle, it will overwrite the one you set in the Coordinator, losing the delegate reference.

3. Confirm the Coordinator's Own Lifecycle

This is a super common pitfall with the Coordinator pattern: if your Coordinator isn't held by a strong reference, it will be deallocated by ARC, making its delegate reference nil.

Bad practice (Coordinator is a temporary variable):

// This Coordinator will be deallocated as soon as this method finishes
func launchListFlow() {
    let listCoordinator = ListCoordinator(navigationController: navController, dependencies: appDependencies)
    listCoordinator.start()
}

Good practice (Coordinator is retained by a parent Coordinator):

class AppCoordinator {
    private let navigationController: UINavigationController
    private var childCoordinators = [Coordinator]() // Holds child Coordinators strongly
    private let appDependencies: AppDependencies

    init(navigationController: UINavigationController, dependencies: AppDependencies) {
        self.navigationController = navigationController
        self.appDependencies = dependencies
    }

    func start() {
        let listCoordinator = ListCoordinator(navigationController: navigationController, dependencies: appDependencies)
        childCoordinators.append(listCoordinator) // Retain the Coordinator
        listCoordinator.start()
    }

    // Don't forget to remove child Coordinators when they finish their flow to avoid memory leaks
    func childCoordinatorDidFinish(_ coordinator: Coordinator) {
        if let index = childCoordinators.firstIndex(where: { $0 === coordinator }) {
            childCoordinators.remove(at: index)
        }
    }
}

4. Debug with Breakpoints

Add breakpoints to verify the delegate assignment works:

  • Put a breakpoint on viewModel.delegate = self in your Coordinator's start() method. Confirm this line executes, and self (the Coordinator instance) is not nil.
  • Put a breakpoint inside userPressed(at:) in ListViewModel. When this hits, check the value of delegate—if it's nil, trace back to see if the Coordinator or ViewModel was deallocated (you can add a deinit method to both classes and print a message to confirm).

内容的提问来源于stack exchange,提问作者testowy

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.29 08:06:13