Refactor ServiceLocator to session-scoped ownership#7
Conversation
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
BaseCoordinator is now pure navigation. Each coordinator that creates ViewModels stores its own serviceLocator reference. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add SessionProvider protocol: auto-provides serviceLocator from session - ViewModels conform to SessionProvider, store session instead of serviceLocator - Coordinators pass session to VMs, no force unwraps - SceneDelegate resolves services directly, no ServiceLocatorProvider conformance - BaseCoordinator is pure navigation, no DI concerns - Tests use MockSession wrapper Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Session is created in init, activated in start(). No separate serviceLocator storage — SessionProvider extension provides it. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
| override public func start() { | ||
| activateSession(for: currentFlow) | ||
| session.activate() | ||
| onSessionActivated?() |
There was a problem hiding this comment.
Why it’s not activateSession?
| // MARK: - ServiceLocatorProvider | ||
|
|
||
| /// Any type that holds a ServiceLocator instance for instance-based DI resolution. | ||
| @MainActor |
There was a problem hiding this comment.
Why need main actor now but not before?
There was a problem hiding this comment.
Claude finished @g-enius's task in 1m 39s —— View job
Why @MainActor was added to ServiceLocatorProvider
Before this PR (main branch): ServiceLocatorProvider had no @MainActor. All concrete conformers (ViewModels) happened to be @MainActor classes, but the protocol requirement itself was nonisolated — Swift accepted this because the concrete types' isolation satisfied the call sites.
What changed: This PR introduced SessionProvider, a @MainActor protocol that inherits from ServiceLocatorProvider and provides a default implementation:
@MainActor
public protocol SessionProvider: ServiceLocatorProvider {
var session: Session { get }
}
extension SessionProvider {
public var serviceLocator: ServiceLocator { session.serviceLocator }
}The Swift 6 strict concurrency rule: A @MainActor-isolated implementation cannot satisfy a nonisolated protocol requirement. Since SessionProvider is @MainActor, its serviceLocator implementation is main-actor-isolated. If ServiceLocatorProvider.serviceLocator remained nonisolated, Swift 6 would error — the protocol witness table requires isolation to match.
Adding @MainActor to ServiceLocatorProvider aligns the requirement's isolation with the implementation's isolation, resolving the violation cleanly.
In short: It wasn't needed before because no @MainActor protocol inherited from it. Once SessionProvider: ServiceLocatorProvider was introduced, Swift 6 required the requirement and implementation to agree on actor isolation.
· Branch
| var session: Session { get } | ||
| } | ||
|
|
||
| extension SessionProvider { |
There was a problem hiding this comment.
Should write protocol conformance here
|
|
||
| private func subscribeToDarkMode() { | ||
| guard let session = appCoordinator?.session else { return } | ||
| let toggles: FeatureToggleServiceProtocol = session.serviceLocator.resolve(for: .featureToggles) |
| // until next AuthenticatedSession.activate() overwrites it | ||
| main.teardown() | ||
| let login = LoginSession(serviceLocator: locator) | ||
| let login = LoginSession() |
There was a problem hiding this comment.
Better named loginSession
|
Claude finished @g-enius's task in 3m 27s —— View job Review Comments FixedAll 5 review comments addressed in commit
|
- Extract activateCurrentSession() helper so start() and activateSession(for:) share one path - Add @mainactor doc comment to ServiceLocatorProvider explaining the isolation requirement - Add MARK comment to SessionProvider extension clarifying ServiceLocatorProvider conformance - Add comment in SceneDelegate explaining why @service can't be used for the dark mode subscription - Rename test variable login → loginSession for clarity Co-authored-by: Charles Wang <g-enius@users.noreply.github.com>
|
Claude finished @g-enius's task in 29m 5s —— View job Sync completeBoth feature branches have been rebased onto the new
What was resolved
|
Summary
Sessionnow creates and owns its ownServiceLocatorinstead of receiving a shared one fromSceneDelegateServiceLocatoris released with the session — no stale services lingering in a shared registrySessionprotocol extendsServiceLocatorProvider;LoginSession/AuthenticatedSessioncreate theirServiceLocatorin-lineSessionFactory.makeSessionno longer takes aserviceLocatorparameterBaseCoordinator.serviceLocatorchanged fromlettointernal(set) varsoAppCoordinatorcan update it on session transitionServiceLocatorProviderprotocol made@MainActor(all conformers already were)SceneDelegatederives itsserviceLocatorfromAppCoordinatorinstead of creating oneTest plan
🤖 Generated with Claude Code