Swift Architecture Standards
Use this skill for pure Swift modules where clean boundaries, concurrency, and testability matter.
1. Clean Architecture Rules (Domain Layer)
When writing Swift business logic (Domain/Core modules):
UseCases (Interactors):
- A UseCase MUST contain only one primary public function:
func execute() async -> Result<T, Error>. - Do NOT use
callAsFunctionfor this to maintain explicit naming. - UseCases MUST always return a standard Swift
Result<T, Error>. - Any external
HelperorManagerMUST be injected into the UseCase, not the Repository.
- A UseCase MUST contain only one primary public function:
Repositories and DataSources:
- Repositories return raw data types (e.g.,
User,[Item]) viaasync throws, NOTResult<T, Error>. The UseCase catches compilation errors and maps them toResult. - Repositories depend ONLY on DataSources. A Repository MUST NOT depend on another Repository.
- DataSources depend only on external APIs/DBs/Network. A DataSource MUST NOT depend on another DataSource.
- Use
protocolfor Repositories to allow easy mocking in unit tests.
- Repositories return raw data types (e.g.,
Dependency Graph:
UI/ViewModel->UseCase->Repository->DataSource
2. Swift Language Patterns
- Concurrency: Prefer modern Swift Concurrency (
async/await,Task,TaskGroup) over GCD (DispatchQueue) or Combine publishers for one-shot operations. - Actors: Use
actorfor shared mutable state to avoid data races. - Value Semantics: Default to
structfor models. UseclassONLY when reference semantics or Identity are strictly required. - Serialization: Use
Codable. - Error Handling Boundary: Do NOT leak raw network or database
Errortypes directly to the View. TheResult<T, Error>from a UseCase must be mapped by the ViewModel/Reducer into specific, UI-friendly State enumerations or One-Time Events. - Swift Strict Concurrency: Code should be safe under strict concurrency checking. Use
@Sendableclosures and explicitly mark isolated boundaries. - Closures: Avoid strong reference cycles by explicitly using
[weak self]in escaping closures. However, withasync/await, prefer async functions over escaping closures to eliminate this risk entirely.