### Example Prompt: Recommend Architecture Source: https://github.com/efremidze/swift-architecture-skill/blob/main/README.md Describe your feature requirements to get an architecture recommendation. This prompt is useful for new feature development. ```text I'm building a SwiftUI feed with pagination, pull-to-refresh, and live updates. Which architecture should I use and why? ``` -------------------------------- ### Example Prompt: Validate Architecture Choice Source: https://github.com/efremidze/swift-architecture-skill/blob/main/README.md Ask for validation on a chosen architecture for a specific feature. This helps ensure the pattern is appropriate and not overkill. ```text We're planning to use TCA for a simple settings screen with two toggles. Is that the right call, or is it overkill for this feature? ``` -------------------------------- ### Add Swift Architecture Skill Source: https://github.com/efremidze/swift-architecture-skill/blob/main/README.md Use this command to add the skill to your project. Ensure npx is installed. ```bash npx skills add https://github.com/efremidze/swift-architecture-skill --skill swift-architecture-skill ``` -------------------------------- ### Example Prompt: Migration Strategy Source: https://github.com/efremidze/swift-architecture-skill/blob/main/README.md Inquire about architectural pattern choices and transition strategies when migrating between UI frameworks (e.g., UIKit to SwiftUI). This prompt addresses coexistence during migration. ```text We have a UIKit + MVP module we're migrating to SwiftUI. Should we keep MVP or switch patterns during the migration, and how do we handle the transition period where both coexist? ``` -------------------------------- ### UIKit Profile Coordinator Example Source: https://github.com/efremidze/swift-architecture-skill/blob/main/swift-architecture-skill/references/coordinator.md An example of a UIKit coordinator managing a profile flow. It uses NavigationRouter to push the ProfileViewController and can launch child coordinators for nested flows like editing a profile. ```swift @MainActor final class ProfileCoordinator: Coordinator { var childCoordinators: [Coordinator] = [] private let router: NavigationRouter private let userRepository: UserRepository init(router: NavigationRouter, userRepository: UserRepository) { self.router = router self.userRepository = userRepository } func start() { let viewModel = ProfileViewModel( repository: userRepository, onEditTapped: { [weak self] in self?.showEditProfile() }, onLogoutTapped: { [weak self] in self?.finish() } ) let viewController = ProfileViewController(viewModel: viewModel) router.push(viewController) } private func showEditProfile() { let editCoordinator = EditProfileCoordinator( router: router, userRepository: userRepository, onComplete: { [weak self] in self?.removeChild($0) } ) addChild(editCoordinator) } private func finish() { // Notify parent this flow is done. } } ``` -------------------------------- ### Example Prompt: Refactor to Clean Architecture Source: https://github.com/efremidze/swift-architecture-skill/blob/main/README.md Request a refactor from an existing pattern (e.g., MVVM) to a more suitable one like Clean Architecture, especially when a ViewModel becomes overloaded. This prompt also asks for layer boundary definitions. ```text This module started as MVVM but the ViewModel is doing too much — routing, formatting, and business logic. Use swift-architecture-skill to refactor it toward Clean Architecture and show me the layer boundaries. ``` -------------------------------- ### MVVM Feature Structure Example Source: https://github.com/efremidze/swift-architecture-skill/blob/main/swift-architecture-skill/references/mvvm.md Illustrative file layout for an MVVM feature slice, showing typical components like View, ViewModel, State, and View Data. ```text App/ Features/ Feed/ FeedView.swift FeedViewModel.swift FeedState.swift FeedViewData.swift FeedDestination.swift FeedAssembly.swift Navigation/ AppRouter.swift DeepLink.swift Domain/ Entities/ UseCases/ Data/ Repositories/ API/ Persistence/ ``` -------------------------------- ### Swift Combine Test Setup Source: https://github.com/efremidze/swift-architecture-skill/blob/main/tooling/evals/benchmarks/cases/reactive-switchlatest-architecture-good.md Sets up a stub search service and a view model test case using Combine and XCTest. This is useful for simulating network responses and testing UI logic. ```swift import Combine import CombineSchedulers import XCTest struct StubSearchService { let search: (String) -> AnyPublisher<[String], Error> } final class SearchViewModelTests: XCTestCase { func test_success_emitsResults() { let output = PassthroughSubject<[String], Error>() let service = StubSearchService { _ in output.eraseToAnyPublisher() } var values: [[String]] = [] let cancellable = service.search("swift") .sink(receiveCompletion: { _ in }, receiveValue: { values.append($0) }) output.send(["latest"]) _ = cancellable XCTAssertEqual(values, [["latest"]]) } func test_failure_emitsFallbackState() { let output = PassthroughSubject<[String], Error>() let service = StubSearchService { _ in output.eraseToAnyPublisher() } var values: [[String]] = [] let cancellable = service.search("swift") .catch { _ in Just(["fallback"]).setFailureType(to: Error.self) } .sink(receiveCompletion: { _ in }, receiveValue: { values.append($0) }) output.send(completion: .failure(TestError.offline)) _ = cancellable XCTAssertEqual(values, [["fallback"]]) } func test_staleResponse_cancelledRequest_usesSwitchToLatest() { let scheduler = DispatchQueue.test let first = PassthroughSubject<[String], Error>() let second = PassthroughSubject<[String], Error>() var values: [[String]] = [] let upstream = PassthroughSubject, Never>() let cancellable = upstream .switchToLatest() .sink(receiveCompletion: { _ in }, receiveValue: { values.append($0) }) scheduler.schedule { upstream.send(first.eraseToAnyPublisher()) upstream.send(second.eraseToAnyPublisher()) first.send(["stale"]) second.send(["latest"]) } scheduler.advance() _ = cancellable XCTAssertEqual(values, [["latest"]]) } } private enum TestError: Error { case offline } ``` -------------------------------- ### ViewModel with NavigationPath (ViewModel-Owned Path) Source: https://github.com/efremidze/swift-architecture-skill/blob/main/swift-architecture-skill/references/mvvm.md Example of a ViewModel managing its own navigation path using an array of `FeedDestination` for ViewModel-owned path management. ```swift @MainActor @Observable final class FeedViewModel { private(set) var state = FeedState() var navigationPath: [FeedDestination] = [] // ...existing properties... func didTapItem(_ item: FeedItemViewData) { navigationPath.append(.detail(id: item.id)) } func didTapProfile(userId: UUID) { navigationPath.append(.profile(userId: userId)) } } ``` -------------------------------- ### MVP Assembly Source: https://context7.com/efremidze/swift-architecture-skill/llms.txt A static method to build the MVP components. It creates the Presenter and the ViewController, then links them together by setting the presenter's view property. This demonstrates a simple dependency injection setup. ```swift // Assembly enum ProfileAssembly { static func build(repository: ProfileRepository) -> UIViewController { let presenter = ProfilePresenter(repository: repository) let vc = ProfileViewController(presenter: presenter) presenter.view = vc // Connect presenter to view return vc } } ``` -------------------------------- ### Coordinator Protocol Definition Source: https://context7.com/efremidze/swift-architecture-skill/llms.txt Defines the basic Coordinator protocol with methods for managing child coordinators and starting a flow. It includes default implementations for adding and removing child coordinators. ```swift // Coordinator protocol @MainActor protocol Coordinator: AnyObject { var childCoordinators: [Coordinator] { get set } func start() } extension Coordinator { func addChild(_ c: Coordinator) { childCoordinators.append(c); c.start() } func removeChild(_ c: Coordinator) { childCoordinators.removeAll { $0 === c } } } ``` -------------------------------- ### SwiftUI Child Coordinator Pattern Example Source: https://github.com/efremidze/swift-architecture-skill/blob/main/swift-architecture-skill/references/coordinator.md Demonstrates a parent coordinator owning and managing child coordinators for nested flows. Child coordinators are added to the parent's childCoordinators array. ```swift @MainActor final class MainCoordinator: Coordinator { var childCoordinators: [Coordinator] = [] private let router: NavigationRouter private let userRepository: UserRepository init(router: NavigationRouter, userRepository: UserRepository) { self.router = router self.userRepository = userRepository } func start() { showHome() } func showHome() { let viewModel = HomeViewModel( onProfileTapped: { [weak self] id in self?.showProfile(userID: id) } ) let viewController = HomeViewController(viewModel: viewModel) router.push(viewController) } private func showProfile(userID: UUID) { let profileRouter = NavigationRouter( navigationController: router.navigationController ) let coordinator = ProfileCoordinator( router: profileRouter, userRepository: userRepository ) addChild(coordinator) } } ``` -------------------------------- ### Adapter for Store Integration Source: https://github.com/efremidze/swift-architecture-skill/blob/main/swift-architecture-skill/references/mvi.md Wires the pure `reduce` and `run` functions into the `Store` by adapting their signatures. ```swift @MainActor func makeCounterStore(service: CounterServicing) -> Store { Store( initial: CounterState(), reduceIntent: { state, intent in guard let effect = reduce(state: &state, intent: intent) else { return nil } return .run { await run(effect, service: service) } }, reduceAction: { state, action in reduce(state: &state, action: action) } ) } ``` -------------------------------- ### Test Cancel In-Flight Effect Source: https://github.com/efremidze/swift-architecture-skill/blob/main/tooling/evals/corpus/cases/tca-effect-cancellation-real-good.md Tests the `cancelInFlight` option, which cancels any previously running effect with the same ID before starting a new one. This ensures only the latest effect is processed. ```swift func testCancelInFlight() async { let values = LockIsolated<[Int]>([]) let subject = PassthroughSubject() let effect1 = Effect.publisher { subject } .cancellable(id: CancelID(), cancelInFlight: true) let task1 = Task { for await n in effect1.actions { values.withValue { $0.append(n) } } } await Task.megaYield() subject.send(1) await Task.megaYield() XCTAssertEqual(values.value, [1]) let effect2 = Effect.publisher { subject } .cancellable(id: CancelID(), cancelInFlight: true) let task2 = Task { for await n in effect2.actions { values.withValue { $0.append(n) } } } await Task.megaYield() subject.send(3) await Task.megaYield() XCTAssertEqual(values.value, [1, 3]) Task.cancel(id: CancelID()) await task1.value await task2.value } ``` -------------------------------- ### Repository Structure Overview Source: https://github.com/efremidze/swift-architecture-skill/blob/main/CONTRIBUTING.md This shows the directory structure of the swift-architecture-skill repository. ```text swift-architecture-skill/ SKILL.md agents/openai.yaml references/ selection-guide.md mvvm.md mvi.md tca.md clean-architecture.md viper.md reactive.md mvp.md coordinator.md ``` -------------------------------- ### Live App Dependencies Implementation Source: https://github.com/efremidze/swift-architecture-skill/blob/main/swift-architecture-skill/references/mvvm.md Concrete implementation of `AppDependencies`, initializing live services like API clients and repositories. ```swift struct LiveDependencies: AppDependencies { private let api: APIClient init(api: APIClient) { self.api = api } var feedRepository: FeedRepository { LiveFeedRepository(api: api) } } ``` -------------------------------- ### Profile Coordinator Unit Tests Source: https://github.com/efremidze/swift-architecture-skill/blob/main/swift-architecture-skill/references/coordinator.md Tests for the ProfileCoordinator, verifying that it correctly pushes the ProfileViewController upon starting and adds a child coordinator when showing the edit profile screen. ```swift @MainActor final class ProfileCoordinatorTests: XCTestCase { func test_start_pushesProfileViewController() { let router = SpyNavigationRouter() let coordinator = ProfileCoordinator( router: router, userRepository: StubUserRepository() ) coordinator.start() XCTAssertEqual(router.pushedViewControllers.count, 1) XCTAssertTrue(router.pushedViewControllers.first is ProfileViewController) } func test_showEditProfile_addsChildCoordinator() { let router = SpyNavigationRouter() let coordinator = ProfileCoordinator( router: router, userRepository: StubUserRepository() ) coordinator.start() coordinator.showEditProfileForTesting() XCTAssertEqual(coordinator.childCoordinators.count, 1) } } ``` -------------------------------- ### Implement Use Case Protocol and Class Source: https://github.com/efremidze/swift-architecture-skill/blob/main/swift-architecture-skill/references/clean-architecture.md Orchestrate business actions through abstractions. Use cases should have a single business responsibility and avoid UI or direct framework usage. ```swift protocol LoadUserUseCase { func execute(id: UUID) async throws -> User } final class LoadUser: LoadUserUseCase { private let repository: UserRepository init(repository: UserRepository) { self.repository = repository } func execute(id: UUID) async throws -> User { try await repository.fetch(id: id) } } ``` -------------------------------- ### Useful Shell Commands Source: https://github.com/efremidze/swift-architecture-skill/blob/main/CONTRIBUTING.md A collection of helpful shell commands for navigating and searching within the repository. ```bash find . -name "*.md" grep -r "pattern" swift-architecture-skill/references/ wc -l swift-architecture-skill/references/*.md ``` -------------------------------- ### Coordinator Protocol Definition Source: https://github.com/efremidze/swift-architecture-skill/blob/main/swift-architecture-skill/references/coordinator.md Defines the base contract for all coordinators, including managing child coordinators and starting the flow. Ensure child coordinators are retained and removed when their flow completes. ```swift @MainActor protocol Coordinator: AnyObject { var childCoordinators: [Coordinator] { get set } func start() } extension Coordinator { func addChild(_ coordinator: Coordinator) { childCoordinators.append(coordinator) coordinator.start() } func removeChild(_ coordinator: Coordinator) { childCoordinators.removeAll { $0 === coordinator } } } ``` -------------------------------- ### Domain Models and Protocols Source: https://github.com/efremidze/swift-architecture-skill/blob/main/tooling/evals/benchmarks/cases/coordinator-navigation-state-good.md Defines the basic data structures and protocols for users and repositories used in the coordinator pattern. ```swift import XCTest // MARK: - Domain struct User { let id: UUID let name: String } protocol UserRepository { func fetchCurrentUser() async throws -> User } struct StubUserRepository: UserRepository { func fetchCurrentUser() async throws -> User { User(id: UUID(uuidString: "00000000-0000-0000-0000-000000000001")!, name: "Stub") } } ``` -------------------------------- ### Mock Profile View Implementation Source: https://github.com/efremidze/swift-architecture-skill/blob/main/swift-architecture-skill/references/viper.md A mock implementation of the ProfileView protocol for testing purposes. It captures UI state changes like loading indicators, displayed names, and error messages. ```swift @MainActor final class MockProfileView: ProfileView { var shownName: String? var shownError: String? var isLoading = false func showLoading(_ isLoading: Bool) { self.isLoading = isLoading } func show(profile: ProfileViewData) { shownName = profile.displayName } func showError(message: String) { shownError = message } } ``` -------------------------------- ### SwiftUI Counter View with WithViewStore (Legacy) Source: https://github.com/efremidze/swift-architecture-skill/blob/main/swift-architecture-skill/references/tca.md A SwiftUI view using the legacy WithViewStore pattern to observe state and send actions. Requires explicit observation of state. ```swift struct CounterView: View { let store: StoreOf var body: some View { WithViewStore(store, observe: { $0 }) { viewStore in VStack { Text("Count: \(viewStore.count)") Button("+") { viewStore.send(.incrementTapped) } Button("-") { viewStore.send(.decrementTapped) } Button("Fact") { viewStore.send(.factButtonTapped) } if viewStore.isLoading { ProgressView() } } .alert(store: store.scope(state: \.alert, action: \.alert)) } } } ``` -------------------------------- ### Assemble Use Case with Live Dependencies Source: https://github.com/efremidze/swift-architecture-skill/blob/main/swift-architecture-skill/references/clean-architecture.md Compose live dependencies in the app or feature assembly layer. Inject protocols into use cases and presentation layers, avoiding global singletons. ```swift enum UserFeatureAssembly { static func makeLoadUserUseCase() -> LoadUserUseCase { let repository = LiveUserRepository(api: .live) return LoadUser(repository: repository) } } ``` -------------------------------- ### Swift UserRepository and Stub Implementation Source: https://github.com/efremidze/swift-architecture-skill/blob/main/tooling/evals/benchmarks/cases/clean-missing-usecase-boundary-should-fail-architecture.md Defines the User model, UserRepository protocol, and a StubUserRepository for testing. Use this stub to simulate different repository outcomes in unit tests. ```swift import XCTest struct User: Equatable { let id: UUID let name: String } protocol UserRepository { func fetch(id: UUID) async throws -> User } struct StubUserRepository: UserRepository { var result: Result func fetch(id: UUID) async throws -> User { try result.get() } } ``` -------------------------------- ### Coordinator Protocol and Extensions Source: https://github.com/efremidze/swift-architecture-skill/blob/main/tooling/evals/benchmarks/cases/coordinator-navigation-state-good.md Defines the core `Coordinator` protocol and provides default implementations for managing child coordinators. ```swift // MARK: - Coordinator @MainActor protocol Coordinator: AnyObject { var childCoordinators: [any Coordinator] { get set } func start() } @MainActor extension Coordinator { func addChild(_ coordinator: any Coordinator) { childCoordinators.append(coordinator) coordinator.start() } func removeChild(_ coordinator: any Coordinator) { childCoordinators.removeAll { ($0 as AnyObject) === (coordinator as AnyObject) } } } ``` -------------------------------- ### Skill Structure Overview Source: https://github.com/efremidze/swift-architecture-skill/blob/main/README.md This structure outlines the organization of the skill's files, including routing logic, references for different architectures, and contribution guidelines. ```text swift-architecture-skill/ SKILL.md # Routing logic and output requirements references/ selection-guide.md # Decision framework across architectures mvp.md # MVP playbook mvvm.md # MVVM playbook mvi.md # MVI playbook tca.md # TCA playbook clean-architecture.md # Clean Architecture playbook viper.md # VIPER playbook coordinator.md # Coordinator playbook reactive.md # Reactive (Combine/RxSwift) playbook ``` -------------------------------- ### Mock and Stub Implementations Source: https://github.com/efremidze/swift-architecture-skill/blob/main/tooling/evals/benchmarks/cases/viper-presenter-wiring-good.md Provides mock and stub implementations for testing: MockProfileView to capture shown names, StubProfileInteractor to control interactor behavior, and SpyProfileRouter to track router interactions. ```swift @MainActor final class MockProfileView: ProfileView { var shownName: String? func show(name: String) { shownName = name } } struct StubProfileInteractor: ProfileInteracting { var load: () async throws -> User func loadUser() async throws -> User { try await load() } } final class SpyProfileRouter: ProfileRouting { var didShowSettings = false func showSettings() { didShowSettings = true } } ``` -------------------------------- ### Run Batch Evaluation Source: https://github.com/efremidze/swift-architecture-skill/blob/main/tooling/evals/README.md Execute the evaluation script to prepare a run package. This command initiates the process for manual mode. ```bash ./tooling/scripts/run/evals.sh ``` -------------------------------- ### UIKit View Implementation Source: https://context7.com/efremidze/swift-architecture-skill/llms.txt A concrete UIKit View that conforms to the `ProfileView` protocol. It purely executes commands received from the Presenter and does not contain presentation logic. Ensure the presenter is injected during initialization. ```swift // UIKit View — purely executes commands @MainActor final class ProfileViewController: UIViewController, ProfileView { private let presenter: ProfilePresenter private let nameLabel = UILabel() // Example UI element init(presenter: ProfilePresenter) { self.presenter = presenter; super.init(nibName: nil, bundle: nil) } @available(*, unavailable) required init?(coder: NSCoder) { fatalError() } // Unavailable designated initializer override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated); presenter.viewDidAppear() // Notify presenter when view appears } // Conformance to ProfileView protocol func showLoading(_ isLoading: Bool) { /* toggle activity indicator */ } func show(profile: ProfileViewData) { nameLabel.text = profile.displayName } // Update UI element func showError(message: String) { /* show error label */ } } ``` -------------------------------- ### Invoke Swift Architecture Skill Source: https://context7.com/efremidze/swift-architecture-skill/llms.txt Invoke the skill from your agent to recommend and scaffold architecture for a feature. ```text Use swift-architecture-skill to recommend and scaffold architecture for this feature. ``` -------------------------------- ### Canonical Combine Pattern for Search Source: https://github.com/efremidze/swift-architecture-skill/blob/main/swift-architecture-skill/references/reactive.md Demonstrates a Combine pipeline for handling user input, debouncing, removing duplicates, and updating results. Use `DispatchQueue.main` as the scheduler in production. Keep cancellables tied to the object's lifecycle. ```swift final class SearchViewModel where S.SchedulerTimeType == DispatchQueue.SchedulerTimeType { @Published var query = "" @Published private(set) var results: [String] = [] private var cancellables = Set() init(service: SearchService, scheduler: S) { $query .debounce(for: .milliseconds(300), scheduler: scheduler) .removeDuplicates() .map { query in service.search(query) .replaceError(with: []) } .switchToLatest() .receive(on: scheduler) .sink { [weak self] values in self?.results = values } .store(in: &cancellables) } } ``` -------------------------------- ### Run Evaluation with Custom Command Source: https://github.com/efremidze/swift-architecture-skill/blob/main/tooling/evals/README.md Run the evaluation script with a custom command that reads from stdin and writes to stdout. The command is executed per case. ```bash ./tooling/scripts/run/evals.sh --cmd "your-command-here" ``` -------------------------------- ### MVI SwiftUI View Source: https://context7.com/efremidze/swift-architecture-skill/llms.txt A SwiftUI view demonstrating how to integrate with an MVI Store to display state and send intents. ```swift // SwiftUI view struct CounterView: View { @StateObject var store: Store var body: some View { VStack { Text("Count: \(store.state.count)") if case .loading = store.state.load { ProgressView() } Button("+") { store.send(.incrementTapped) } } } } ``` -------------------------------- ### Implement Profile Presenter Source: https://github.com/efremidze/swift-architecture-skill/blob/main/swift-architecture-skill/references/viper.md Implements the ProfilePresenter class, handling user interactions, business logic requests via the Interactor, and navigation via the Router. It manages asynchronous loading and updates the View. Ensure the 'view' property is weak to prevent retain cycles and perform UI updates on the main actor. ```swift @MainActor final class ProfilePresenter { weak var view: ProfileView? private let interactor: ProfileInteracting private let router: ProfileRouting private var loadTask: Task? private var latestLoadRequestID: UUID? init(interactor: ProfileInteracting, router: ProfileRouting) { self.interactor = interactor self.router = router } func load() { let requestID = UUID() latestLoadRequestID = requestID loadTask?.cancel() view?.showLoading(true) loadTask = Task { do { let user = try await interactor.loadUser() try Task.checkCancellation() guard latestLoadRequestID == requestID else { return } view?.show(profile: ProfileViewData(user: user)) } catch is CancellationError { // Cancelled by a newer load request. } catch { guard latestLoadRequestID == requestID else { return } view?.showError(message: "Failed to load profile. Please try again.") } guard latestLoadRequestID == requestID else { return } view?.showLoading(false) } } func didTapSettings() { router.showSettings() } deinit { loadTask?.cancel() } } ``` -------------------------------- ### Define Domain and View Models Source: https://github.com/efremidze/swift-architecture-skill/blob/main/swift-architecture-skill/references/viper.md Defines the User entity and a ProfileViewData model, including an extension to map User to ProfileViewData. Use these for domain data and display-specific data respectively. ```swift struct User: Equatable { let id: UUID let name: String let isPremium: Bool } struct ProfileViewData: Equatable { let displayName: String let badgeText: String? } extension ProfileViewData { init(user: User) { self.displayName = user.name self.badgeText = user.isPremium ? "Premium" : nil } } ``` -------------------------------- ### Profile Presenter Implementation Source: https://github.com/efremidze/swift-architecture-skill/blob/main/tooling/evals/benchmarks/cases/viper-presenter-wiring-good.md Implements the ProfilePresenter class, handling user data loading, view updates, and routing to settings. It manages asynchronous operations and error handling, including cancellation. ```swift @MainActor final class ProfilePresenter { weak var view: ProfileView? private let interactor: ProfileInteracting private let router: ProfileRouting init(interactor: ProfileInteracting, router: ProfileRouting) { self.interactor = interactor self.router = router } func load() async { do { let user = try await interactor.loadUser() view?.show(name: user.name) } catch is CancellationError { // keep existing UI state on cancellation } catch { view?.show(name: "") } } func didTapSettings() { router.showSettings() } } ``` -------------------------------- ### Clean Architecture Assembly Source: https://context7.com/efremidze/swift-architecture-skill/llms.txt Provides a composition root for assembling the Clean Architecture components, specifically creating an instance of the LoadUserUseCase. ```swift // Assembly — composition root enum UserFeatureAssembly { static func makeLoadUserUseCase() -> LoadUserUseCase { LoadUser(repository: LiveUserRepository(api: .live)) } } ``` -------------------------------- ### Concrete Coordinator Implementation Source: https://github.com/efremidze/swift-architecture-skill/blob/main/swift-architecture-skill/references/mvvm.md Implement the Coordinator protocol in a concrete class, typically within the navigation layer. This class handles the actual presentation of ViewControllers or SwiftUI Views. ```swift @MainActor final class FeedFlowCoordinator: FeedCoordinator { private let navigationController: UINavigationController init(navigationController: UINavigationController) { self.navigationController = navigationController } func showDetail(itemID: UUID) { let viewModel = FeedDetailAssembly.makeViewModel(itemID: itemID) let vc = UIHostingController(rootView: FeedDetailView(viewModel: viewModel)) navigationController.pushViewController(vc, animated: true) } func showProfile(userId: UUID) { let viewModel = ProfileAssembly.makeViewModel(userId: userId) let vc = UIHostingController(rootView: ProfileView(viewModel: viewModel)) navigationController.pushViewController(vc, animated: true) } func presentCompose(onComplete: @MainActor @escaping () -> Void) { let composeVM = ComposeAssembly.makeViewModel(onComplete: onComplete) let vc = UIHostingController(rootView: ComposeView(viewModel: composeVM)) navigationController.present(vc, animated: true) } } ``` -------------------------------- ### Stub User Repository Implementation Source: https://github.com/efremidze/swift-architecture-skill/blob/main/swift-architecture-skill/references/coordinator.md A stub implementation of the UserRepository protocol for use in tests. It provides a mock User object for fetching the current user. ```swift struct StubUserRepository: UserRepository { func fetchCurrentUser() async throws -> User { User(id: UUID(), name: "Stub", isPremium: false, joinDate: .now) } } ``` -------------------------------- ### Combine ViewModel with Injectable Scheduler Source: https://context7.com/efremidze/swift-architecture-skill/llms.txt Implement a ViewModel using Combine that allows for an injectable scheduler, enabling debouncing, duplicate removal, and error handling for search queries. This pattern is useful for stream-heavy features like typeahead search. ```swift final class SearchViewModel: ObservableObject where S.SchedulerTimeType == DispatchQueue.SchedulerTimeType { @Published var query = "" @Published private(set) var results: [String] = [] private var cancellables = Set() init(service: SearchService, scheduler: S) { $query .debounce(for: .milliseconds(300), scheduler: scheduler) .removeDuplicates() .map { query in service.search(query).replaceError(with: []) } .switchToLatest() .receive(on: scheduler) .sink { [weak self] values in self?.results = values } .store(in: &cancellables) } } ``` -------------------------------- ### MVP Anti-Pattern: Direct Repository Testing Source: https://github.com/efremidze/swift-architecture-skill/blob/main/tooling/evals/benchmarks/cases/mvp-missing-presenter-wiring-should-fail-architecture.md This Swift code demonstrates an anti-pattern where repository stubs are tested directly, bypassing the Presenter and mock View. This is used to highlight a failure in following MVP architecture principles. ```swift import XCTest struct User { let id: UUID let name: String } struct ProfileViewData: Equatable { let displayName: String } protocol ProfileRepository { func fetchCurrentUser() async throws -> User } struct StubProfileRepository: ProfileRepository { var result: Result func fetchCurrentUser() async throws -> User { try result.get() } } // Missing pattern: tests exercise the repository stub directly // instead of going through a Presenter wired to a mock View. @MainActor final class ProfileRepositoryTests: XCTestCase { func test_fetch_success_returnsUser() async throws { let repo = StubProfileRepository(result: .success(User(id: UUID(), name: "Alice"))) let user = try await repo.fetchCurrentUser() XCTAssertEqual(user.name, "Alice") } func test_fetch_failure_throws() async { let repo = StubProfileRepository(result: .failure(TestError.offline)) do { _ = try await repo.fetchCurrentUser() XCTFail("Expected error") } catch { XCTAssertTrue(error is TestError) } } func test_fetch_cancellation_throwsCancellationError() async { let repo = StubProfileRepository(result: .failure(CancellationError())) do { _ = try await repo.fetchCurrentUser() XCTFail("Expected cancellation") } catch is CancellationError { XCTAssertTrue(true) } catch { XCTFail("Unexpected error: \(error)") } } } private enum TestError: Error { case offline } ``` -------------------------------- ### App Container for ViewModels Source: https://github.com/efremidze/swift-architecture-skill/blob/main/swift-architecture-skill/references/mvvm.md Manages the application's dependency graph and provides factory methods for creating ViewModels, injecting necessary dependencies. ```swift @MainActor final class AppContainer { private let dependencies: AppDependencies init(dependencies: AppDependencies) { self.dependencies = dependencies } func makeFeedViewModel() -> FeedViewModel { FeedViewModel(repository: dependencies.feedRepository) } } ``` -------------------------------- ### Swift Clean Architecture Use Case and Repository Stubs Source: https://github.com/efremidze/swift-architecture-skill/blob/main/tooling/evals/benchmarks/cases/clean-usecase-boundary-good.md Defines a User model, a UserRepository protocol, and a LoadUser use case. Includes a StubUserRepository for testing and a BlockingUserRepository for cancellation tests. ```swift import XCTest struct User: Equatable { let id: UUID let name: String } protocol UserRepository { func fetch(id: UUID) async throws -> User } struct LoadUser { let repository: UserRepository func execute(id: UUID) async throws -> User { try await repository.fetch(id: id) } } struct StubUserRepository: UserRepository { var result: Result func fetch(id: UUID) async throws -> User { try result.get() } } @MainActor final class LoadUserTests: XCTestCase { func test_execute_success_returnsUser() async throws { let expected = User(id: UUID(), name: "Alice") let sut = LoadUser(repository: StubUserRepository(result: .success(expected))) let user = try await sut.execute(id: expected.id) XCTAssertEqual(user, expected) } func test_execute_failure_propagatesError() async { let sut = LoadUser(repository: StubUserRepository(result: .failure(TestError.notFound))) do { _ = try await sut.execute(id: UUID()) XCTFail("Expected failure") } catch { XCTAssertTrue(error is TestError) } } func test_execute_cancellation_cancelledTask_throwsCancellation() async { let sut = LoadUser(repository: BlockingUserRepository()) let task = Task { try await sut.execute(id: UUID()) } task.cancel() do { _ = try await task.value XCTFail("Expected cancellation") } catch is CancellationError { XCTAssertTrue(true) } catch { XCTFail("Unexpected error: \(error)") } } } actor BlockingUserRepository: UserRepository { func fetch(id: UUID) async throws -> User { await Task.yield() return User(id: id, name: "") } } private enum TestError: Error { case notFound } ``` -------------------------------- ### MVI Store Implementation Source: https://github.com/efremidze/swift-architecture-skill/blob/main/swift-architecture-skill/references/mvi.md The core `Store` class manages state, processes intents, handles effects, and dispatches actions. It runs on the main actor for UI mutation safety and supports cancellation and versioning for concurrent requests. ```swift @MainActor final class Store: ObservableObject { @Published private(set) var state: State private let reduceIntent: (inout State, Intent) -> Effect? private let reduceAction: (inout State, Action) -> Void private let onUnexpectedError: @MainActor (Error) -> Void private var activeTasks: [AnyHashable: Task] = [: ] init( initial: State, reduceIntent: @escaping (inout State, Intent) -> Effect?, reduceAction: @escaping (inout State, Action) -> Void, onUnexpectedError: @escaping @MainActor (Error) -> Void = { error in assertionFailure("Unexpected unmodeled effect error: \(error)") } ) { self.state = initial self.reduceIntent = reduceIntent self.reduceAction = reduceAction self.onUnexpectedError = onUnexpectedError } func send(_ intent: Intent) { guard let effect = reduceIntent(&state, intent) else { return } handle(effect) } private func handle(_ effect: Effect) { switch effect { case .none: break case .run(let operation): Task { do { let action = try await operation() reduceAction(&state, action) } catch is CancellationError { // Task was cancelled; no state update. } catch { onUnexpectedError(error) } } case .cancellable(let id, let operation): activeTasks[id]?.cancel() activeTasks[id] = Task { do { let action = try await operation() reduceAction(&state, action) } catch is CancellationError { // Cancelled by a newer request for the same id. } catch { onUnexpectedError(error) } activeTasks[id] = nil } } } deinit { for task in activeTasks.values { task.cancel() } } } ``` -------------------------------- ### Spy Navigation Router for Testing Source: https://github.com/efremidze/swift-architecture-skill/blob/main/swift-architecture-skill/references/coordinator.md A mock implementation of NavigationRouter used for testing coordinator navigation logic. It records pushed and presented view controllers for assertion. ```swift @MainActor final class SpyNavigationRouter: NavigationRouter { var pushedViewControllers: [UIViewController] = [] var presentedViewControllers: [UIViewController] = [] override func push(_ viewController: UIViewController, animated: Bool = true) { pushedViewControllers.append(viewController) } override func present(_ viewController: UIViewController, animated: Bool = true) { presentedViewControllers.append(viewController) } } ``` -------------------------------- ### Presenter Settings Navigation Test Source: https://github.com/efremidze/swift-architecture-skill/blob/main/swift-architecture-skill/references/viper.md Tests the ProfilePresenter's navigation logic when the settings button is tapped. It verifies that the router's `showSettings` method is invoked. ```swift func test_didTapSettings_routesToSettings() { let router = SpyProfileRouter() let presenter = ProfilePresenter( interactor: StubProfileInteractor(load: { User(id: UUID(), name: "", isPremium: false) }), router: router ) presenter.didTapSettings() XCTAssertTrue(router.didShowSettings) } ``` -------------------------------- ### ChildFlowCoordinator Implementation Source: https://github.com/efremidze/swift-architecture-skill/blob/main/tooling/evals/benchmarks/cases/coordinator-navigation-state-good.md A sample implementation of a child coordinator used for testing the lifecycle management within the `AppCoordinator`. ```swift @MainActor final class ChildFlowCoordinator: Coordinator { var childCoordinators: [any Coordinator] = [] var didStart = false func start() { didStart = true } } ``` -------------------------------- ### UIKit View Controller Implementation Source: https://github.com/efremidze/swift-architecture-skill/blob/main/swift-architecture-skill/references/mvp.md A UIKit ViewController that conforms to the ProfileView protocol, forwarding actions to its presenter and executing view commands. It sets up basic UI elements and handles view lifecycle events. ```swift @MainActor final class ProfileViewController: UIViewController, ProfileView { private let presenter: ProfilePresenter private let nameLabel = UILabel() private let activityIndicator = UIActivityIndicatorView(style: .medium) private let errorLabel = UILabel() init(presenter: ProfilePresenter) { self.presenter = presenter super.init(nibName: nil, bundle: nil) } @available(*, unavailable) required init?(coder: NSCoder) { fatalError() } override func viewDidLoad() { super.viewDidLoad() setupLayout() } override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) presenter.viewDidAppear() } // MARK: - ProfileView func showLoading(_ isLoading: Bool) { isLoading ? activityIndicator.startAnimating() : activityIndicator.stopAnimating() } func show(profile: ProfileViewData) { nameLabel.text = profile.displayName errorLabel.isHidden = true } func showError(message: String) { errorLabel.text = message errorLabel.isHidden = false } private func setupLayout() { // Layout setup omitted for brevity. } } ``` -------------------------------- ### MVP Presenter Unit Tests Source: https://github.com/efremidze/swift-architecture-skill/blob/main/swift-architecture-skill/references/mvp.md Tests for a ProfilePresenter using a mock view and stub repository. Ensures success, failure, and cancellation paths are handled correctly. Use `Task.yield()` to allow async operations to complete. ```swift @MainActor final class MockProfileView: ProfileView { var isLoading = false var shownViewData: ProfileViewData? var shownError: String? func showLoading(_ isLoading: Bool) { self.isLoading = isLoading } func show(profile: ProfileViewData) { shownViewData = profile } func showError(message: String) { shownError = message } } struct StubProfileRepository: ProfileRepository { var result: Result func fetchCurrentUser() async throws -> User { try result.get() } } @MainActor final class ProfilePresenterTests: XCTestCase { func test_load_success_showsUserName() async { let user = User(id: UUID(), name: "Alice", isPremium: false, joinDate: .now) let view = MockProfileView() let presenter = ProfilePresenter( repository: StubProfileRepository(result: .success(user)) ) presenter.view = view presenter.load() await Task.yield() XCTAssertEqual(view.shownViewData?.displayName, "Alice") XCTAssertNil(view.shownError) } func test_load_failure_showsError() async { let view = MockProfileView() let presenter = ProfilePresenter( repository: StubProfileRepository(result: .failure(TestError.notFound)) ) presenter.view = view presenter.load() await Task.yield() XCTAssertNotNil(view.shownError) XCTAssertNil(view.shownViewData) } func test_load_cancellation_doesNotOverwriteExistingViewData() async { let existing = User(id: UUID(), name: "Existing", isPremium: false, joinDate: .now) let view = MockProfileView() view.show(profile: ProfileViewData(user: existing)) let presenter = ProfilePresenter( repository: StubProfileRepository(result: .failure(CancellationError())) ) presenter.view = view presenter.load() await Task.yield() XCTAssertEqual(view.shownViewData?.displayName, "Existing") } func test_rapidLoads_onlyLatestResultShown() async { let firstUser = User(id: UUID(), name: "First", isPremium: false, joinDate: .now) let view = MockProfileView() let presenter = ProfilePresenter( repository: StubProfileRepository(result: .success(firstUser)) ) presenter.view = view // Simulate two rapid loads; second call cancels first. presenter.load() // request A — will be cancelled presenter.load() // request B — latest await Task.yield() await Task.yield() XCTAssertEqual(view.shownViewData?.displayName, "First") } } private enum TestError: Error { case notFound } ``` -------------------------------- ### MVP Presenter Implementation Source: https://context7.com/efremidze/swift-architecture-skill/llms.txt The Presenter owns presentation logic and drives the View via commands. It manages asynchronous operations and handles cancellation of stale requests. Ensure the repository dependency is provided during initialization. ```swift // Presenter — drives View via commands, cancels stale requests @MainActor final class ProfilePresenter { weak var view: ProfileView? // Weak reference to avoid retain cycles private let repository: ProfileRepository private var loadTask: Task? private var latestRequestID: UUID? // To track and cancel stale requests init(repository: ProfileRepository) { self.repository = repository } func viewDidAppear() { load() } // Trigger load when view appears func load() { let requestID = UUID() // Unique ID for this request latestRequestID = requestID loadTask?.cancel() // Cancel any previous ongoing task view?.showLoading(true) // Show loading indicator loadTask = Task { do { let user = try await repository.fetchCurrentUser() try Task.checkCancellation() // Check if task was cancelled guard latestRequestID == requestID else { return } // Ignore if a newer request is active view?.show(profile: ProfileViewData(user: user)) // Update view with profile data } catch is CancellationError { // Task was cancelled, do nothing } catch { guard latestRequestID == requestID else { return } // Ignore if a newer request is active view?.showError(message: "Failed to load profile. Please try again.") // Show error message } guard latestRequestID == requestID else { return } // Ensure this is the latest request before hiding loading view?.showLoading(false) // Hide loading indicator } } deinit { loadTask?.cancel() } // Cancel task on deinitialization } ``` -------------------------------- ### MVVM Testing Utilities Source: https://github.com/efremidze/swift-architecture-skill/blob/main/swift-architecture-skill/references/mvvm.md Provides a controllable repository stub and test error enum for MVVM testing. Use protocol stubs/fakes for repositories and avoid sleep-based tests by using controllable stub responses. ```swift import XCTest struct FeedItem: Equatable { let id: UUID let title: String } struct FeedPage: Equatable { let items: [FeedItem] } extension FeedItemViewData { init(_ item: FeedItem) { self.id = item.id self.title = item.title } } actor ControlledFeedRepository: FeedRepository { private var continuations: [CheckedContinuation] = [] func fetchPage(cursor: String?) async throws -> FeedPage { try await withCheckedThrowingContinuation { continuation in continuations.append(continuation) } } func resolveNext(with result: Result) { guard !continuations.isEmpty else { return } let continuation = continuations.removeFirst() switch result { case .success(let page): continuation.resume(returning: page) case .failure(let error): continuation.resume(throwing: error) } } } private enum TestError: Error { case offline } ``` -------------------------------- ### MVP Assembly for UIKit Source: https://github.com/efremidze/swift-architecture-skill/blob/main/swift-architecture-skill/references/mvp.md A static method to build the MVP module for UIKit, creating the Presenter, ViewController, and linking them. It ensures dependencies like repositories are injected. ```swift enum ProfileAssembly { static func build(repository: ProfileRepository) -> UIViewController { let presenter = ProfilePresenter(repository: repository) let viewController = ProfileViewController(presenter: presenter) presenter.view = viewController return viewController } @MainActor static func buildSwiftUI(repository: ProfileRepository) -> ProfileScreen { let presenter = ProfilePresenter(repository: repository) let adapter = ProfileViewAdapter(presenter: presenter) return ProfileScreen(adapter: adapter) } } ``` -------------------------------- ### Map User to View Data Source: https://github.com/efremidze/swift-architecture-skill/blob/main/swift-architecture-skill/references/mvp.md Extension to map a domain `User` object to the display-ready `ProfileViewData` structure within the Presenter. ```swift extension ProfileViewData { init(user: User) { self.displayName = user.name self.badgeText = user.isPremium ? "Premium" : nil self.formattedJoinDate = user.joinDate.formatted(.dateTime.year().month()) } } ``` -------------------------------- ### DependencyKey for NumberFactClient Source: https://github.com/efremidze/swift-architecture-skill/blob/main/swift-architecture-skill/references/tca.md Defines live and test implementations for the `NumberFactClient` dependency, conforming to `DependencyKey`. ```swift struct NumberFactClient { var fetch: @Sendable (Int) async throws -> String } extension NumberFactClient: DependencyKey { static let liveValue = Self(fetch: { number in "\(number) is a good number." }) static let testValue = Self(fetch: { _ in "Test fact" }) } extension DependencyValues { var numberFact: NumberFactClient { get { self[NumberFactClient.self] } set { self[NumberFactClient.self] = newValue } } } ``` -------------------------------- ### SwiftUI Adapter for MVP Source: https://context7.com/efremidze/swift-architecture-skill/llms.txt An adapter class that bridges the MVP Presenter with a SwiftUI View. It uses `@Observable` to expose state to SwiftUI and implements the `ProfileView` protocol to receive commands from the Presenter. The presenter is set as the view's delegate. ```swift // SwiftUI adapter for MVP @MainActor @Observable final class ProfileViewAdapter: ProfileView { private(set) var viewData: ProfileViewData? // State exposed to SwiftUI private(set) var isLoading = false private(set) var errorMessage: String? private let presenter: ProfilePresenter init(presenter: ProfilePresenter) { self.presenter = presenter; presenter.view = self // Set self as the view delegate } // Conformance to ProfileView protocol func showLoading(_ isLoading: Bool) { self.isLoading = isLoading } func show(profile: ProfileViewData) { viewData = profile; errorMessage = nil } func showError(message: String) { errorMessage = message } // Method to trigger presenter action from SwiftUI func viewDidAppear() { presenter.viewDidAppear() } } ```