top of page

CFA Reference: Overview

Formal CFA AI Toolkit for iOS Documentation · Reference 1 of 26

Cooperative Feature Architecture (CFA) is a small, ownership-based architecture for SwiftUI apps written in Swift 6 with Swift Concurrency. The CFA AI Toolkit for iOS packages CFA as eight AI skills, Xcode templates, starter apps and one local analysis tool, so an AI coding assistant can create, change, migrate, review and test an iOS app in a consistent way. This page is the entry point to the 26 formal reference pages.

Definitions

  • CFA: Cooperative Feature Architecture. Every type of code has one clear home and every piece of mutable state has one owner.
  • CFA AI Toolkit for iOS: The open-source repository 3DaysOfSwift/CFA-AI-Toolkit-for-iOS. It contains the specification, coding guide, change gate, skills, templates, starter apps and the Xcode Project Dashboard.
  • Layer path: View → ViewModel → Feature API → Feature → Repository → External System. Calls travel left to right; observed state travels back.
  • Feature: A concrete, cohesive owner of one area of business rules and feature state, for example HabitsFeature or SettingsFeature.
  • AppModel: The composition root. It creates the real Features and repositories in one place and exposes them through a single shared instance.
  • Change gate: The completion checklist in architecture/cfa-change-gate.md. A CFA change is not finished until the gate passes.

Rules

  1. Treat architecture/cfa-specification.md as the canonical architecture; the canonical rules live there and in architecture/swift-coding-guide.md.
  2. Name each concrete owner with a Feature suffix, such as SettingsFeature, and use the matching settingsFeature name for the AppModel property, ViewModel dependency and initialiser label.
  3. Give each piece of mutable state exactly one owner. A Feature owns domain data, operation state and errors; a ViewModel owns screen state only.
  4. Keep the path through a feature readable in one pass: View, its dedicated ViewModel, the Feature, then the repository.
  5. Use Swift Concurrency for all asynchronous work. Do not use locks, GCD inside async code, or unsafe opt-outs.
  6. Mark Features and ViewModels @MainActor explicitly. Put shared mutable storage behind actors and pass Sendable values between them.
  7. Finish every change by passing the change gate, including a build and the tests at the final commit.

Decision rules

  • When you start a new product, use CFA App Creation and the CFA App template.
  • When you add, change or remove a feature in a CFA app, use CFA Feature Work.
  • When the app uses GCD, operation queues or callbacks, upgrade the whole app to CFA and Swift Concurrency in one move with the Legacy GCD migration skill.
  • When you only want to know how healthy the code is, use CFA Architecture Review, which changes nothing.
  • When you are unsure which page to read next, go to Reference 12: Decision Rules.

Example

The smallest complete CFA graph is one value type, one repository, one Feature, the AppModel and one ViewModel. This excerpt shows the Feature that owns the data; the full compiled example appears in Reference 4 and Reference 5.

Compiled example (excerpt of a larger file)

import Foundation
import Observation

// MARK: Feature: the authoritative owner of habits
@MainActor
@Observable
final class HabitsFeature {
    @ObservationIgnored private var refreshTask: Task<Void, Never>?
    private let repository: any HabitsRepository
    private(set) var habits: [Habit] = []
    private(set) var isLoading = false
    private(set) var loadError: String?

    init(repository: any HabitsRepository) {
        self.repository = repository
    }

    /// Synchronous launch entry point: initiates work and returns.
    func startLoading() {
        _ = sharedRefreshTask()
    }

    /// Every caller awaits the one in-flight refresh.
    func refresh() async {
        await sharedRefreshTask().value
    }

    private func sharedRefreshTask() -> Task<Void, Never> {
        if let refreshTask { return refreshTask }
        let newRefreshTask = Task { [repository] in
            await self.performRefresh(using: repository)
        }
        refreshTask = newRefreshTask
        return newRefreshTask
    }

    private func performRefresh(using repository: any HabitsRepository) async {
        defer { refreshTask = nil }
        isLoading = true
        defer { isLoading = false }
        do {
            habits = try await repository.fetchHabits()
            loadError = nil
        } catch {
            loadError = error.localizedDescription
        }
    }

    func addHabit(named name: String) async throws {
        let cleanedName = name.trimmingCharacters(in: .whitespacesAndNewlines)
        guard !cleanedName.isEmpty else { throw HabitError.invalidName }
        let habit = Habit(id: UUID(), name: cleanedName)
        try await repository.insertHabit(habit)
        habits.append(habit)
    }
}

// (HabitsRepository and Habit are defined in Reference 6.)

Compiled with Swift 6 language mode and warnings treated as errors: type-checked on Swift 6.2 and built on Swift 6.4 (Linux toolchains, Foundation and Observation only).

Common mistakes

  • Treating CFA as a framework to import. CFA is a set of rules and plain Swift types; there is no library dependency.
  • Adding a protocol for every Feature. CFA Features are concrete; protocols belong at repository boundaries.
  • Letting a View talk to a repository directly. The View talks only to its ViewModel.
  • Calling a change finished because it compiles. The change gate also asks for tests, inventories and a human review.

Links

bottom of page