Home / Blog / Clean Architecture in iOS apps: separation of concerns in practice

Clean Architecture in iOS apps: separation of concerns in practice

Apps grow. What starts as a two-screen project accumulates business rules, integrations, state and edge cases. Without structure, the code turns into a web where changing one thing breaks another.

Clean Architecture, proposed by Robert C. Martin, splits code into layers with well-defined responsibilities. The central rule: dependencies only point inward. The domain layer doesn’t know SwiftUI exists, nor the database, nor the API.

The three layers

Domain

The heart of the app. It contains:

No framework dependencies. Pure Swift. Testable without UIKit or SwiftUI mocks.

Data

Implements the domain’s protocols:

Presentation

The actual SwiftUI layer:

It depends only on use cases. It never makes network calls directly.

In practice: an example

In FinPessoal, the flow for recording a transaction is:

  1. The user fills in the form → the view calls CreateTransactionUseCase
  2. The use case validates the entity and calls TransactionRepository.save()
  3. The repository delegates to LocalDataSource.insert(transactionModel)
  4. Success or error travels back the same way via async throws

None of these layers knows the internal implementation of the others — only the protocols.

The payoff in testing

With this separation, testing a use case is straightforward:

func testRejectsTransactionWithZeroAmount() async throws {
    let useCase = CreateTransactionUseCase(
        repository: MockTransactionRepository()
    )

    await #expect(throws: InvalidAmountError.self) {
        try await useCase.execute(
            Transaction(amount: 0, description: "Test")
        )
    }
}

No simulator. No real database. Just business logic.

The same goes for data sources: you test Codable serialization without depending on a real network. And views are tested without depending on business rules.

When to apply it

Clean Architecture doesn’t need to be applied rigidly in small apps. In projects with fewer than ten screens and no complex business logic, a simpler split (plain MVVM with @Observable) can be enough.

The layered architecture pays off when:

Conclusion

Clean Architecture isn’t overhead for small apps — it’s what keeps small apps small as they grow. Separating layers is what lets us add a new feature without fear of breaking what already works.

It’s the pattern we use in every werkStadtG project, and the one we recommend for any iOS app that’s meant to last.