final class HomeInteractor: PresentableInteractor<HomePresentable> | final actor HomeInteractor: PresentableInteractable, HomePresentableListener | Actors can’t subclass; PresentableInteractable is a protocol with default lifecycle implementations. See Protocol Composition Over Inheritance. |
super.init(presenter: presenter) | nonisolated let presenter: HomePresentable; init(presenter: ...) { self.presenter = presenter } | No base class to call into. The presenter is a stored nonisolated property declared by PresentableInteractable. |
| (none) | nonisolated let lifecycle = InteractorLifecycle() | Required by Interactable. Holds active-state, bound tasks, and the AsyncStream continuations. |
private var cancellables = Set<AnyCancellable>() | (deleted) | Combine is gone. Bound tasks via task(priority:_:) replace disposeOnDeactivate. |
override func didBecomeActive() { super.didBecomeActive(); … } | func didBecomeActive() async { … } | No super to call. didBecomeActive()’s default impl is a no-op; your body is the override. |
userPublisher.sink { … }.store(in: &cancellables) | task { for await user in userService.userStream() { … } } | for await over an AsyncStream replaces Combine subscription. The task { ... } binds it to the active scope. |
override func willResignActive() { super.willResignActive(); cancellables.removeAll() } | func willResignActive() async { … } | No manual task cancellation — the lifecycle cancels bound tasks automatically after willResignActive() returns. |
presenter.update(user: user) (sync) | await presenter.update(user: user) | Presenter is @MainActor; the interactor is an actor. Crossing requires await. |
init(...) { … presenter.listener = self } | await MainActor.run { presenter.listener = self } inside didBecomeActive() | The presenter is @MainActor; setting its listener requires being on the main actor. Doing it in didBecomeActive() (rather than init) also keeps the wiring tied to the active scope. |
router?.routeToProfile() (sync) | await router?.routeToProfile() | Router is @MainActor. |
protocol HomeListener { func homeDidLogout() } | protocol HomeListener: AnyObject, Sendable { func homeDidLogout() async } | Listeners cross isolation domains (parent’s actor). They are Sendable and their methods are async. |
attachChild(r) (sync) | await attachChild(r) | attachChild(_:) is async because it awaits the child’s activate(). |
super.init(interactor: interactor, viewController: viewController) in router | Same | ViewableRouter is still a class; this initializer is unchanged. |
CurrentValueSubject on a service | actor service vending replay-latest AsyncStreams (fresh stream per subscriber) | Producer recipes in Streaming State Down the Tree. |
PassthroughSubject | The same fan-out actor, minus the initial yield | No replay. |