Swift Weak Delegate始终为nil求助:Coordinator回调失败
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
listViewModelproperty inListVCuses a strong reference (the default in Swift, noweakmodifier). 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
ListViewModelelsewhere inListVC. 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 = selfin your Coordinator'sstart()method. Confirm this line executes, andself(the Coordinator instance) is not nil. - Put a breakpoint inside
userPressed(at:)inListViewModel. When this hits, check the value ofdelegate—if it's nil, trace back to see if the Coordinator or ViewModel was deallocated (you can add adeinitmethod to both classes and print a message to confirm).
内容的提问来源于stack exchange,提问作者testowy

