Clean Architecture + SwiftUI

maykonmeneghel
3 min readJun 13, 2023

What is Clean Architecture?

Clean Architecture is a software architecture pattern that emphasizes separation of concerns, maintainability, and testability. It enables developers to build applications that are decoupled from external frameworks, making them easier to modify, test, and scale.

Clean Architecture consists of four main layers: Domain, Infra, External, and Presentation. Each layer has a specific responsibility and interacts with the adjacent layers through defined boundaries.

Clean Architecture in SwiftUI

When applying Clean Architecture principles to SwiftUI, we can achieve a well-structured and modular codebase that is easily maintainable and extensible. Let’s explore the different layers and their components in the context of SwiftUI.

Domain Layer

The Domain layer represents the core of our application’s business logic. It is independent of any framework and consists of the following components:

  • Entities: Defines the data models that represent the domain entities. These models encapsulate the essential properties and behaviors of our application’s core concepts.
  • Repositories: Interfaces that define the operations for accessing domain data. Repositories act as gateways between the domain layer and the infrastructure layer, abstracting away the implementation details.
  • UseCases: Implementations of the use cases that contain the application’s business logic. UseCases orchestrate the interactions between entities and repositories to fulfill specific user requirements.

Here’s an example of a Domain layer implementation for a SwiftUI app:

// Domain/Entities/ContentEntity.swift
struct ContentEntity {
var theme: String
var url: String
var level: String
}

// Domain/Repositories/ContentRepository.swift
protocol ContentRepository {
func fetchContents() -> [ContentEntity]
}

// Domain/UseCases/FetchContentsUseCase.swift
protocol FetchContentsUseCase {
func call() -> [ContentEntity]
}

struct FetchContentsUseCaseImpl: FetchContentsUseCase {
var repository: ContentRepository

func call() -> [ContentEntity] {
repository.fetchContents()
}
}

Infra Layer

The Infra layer is responsible for implementing the operations for accessing domain data defined in the Domain layer. It contains the following components:

  • Datasource: Interfaces that define data retrieval, both locally and remotely. Datasources can interact with databases, APIs, or any other external data sources.
  • Models: Defines the data models specific to the Infra layer. These models are tailored to the data storage and retrieval mechanisms used in the infrastructure layer.
  • Repositories: Implementations of the repositories defined in the Domain layer. These implementations interact with the appropriate datasources to fetch and persist data.
  • Mappers: Mapping between the domain data models and the Infra layer data models. Mappers ensure a clean separation between the domain and infrastructure layers and handle the conversion of data models as needed.

Here’s an example of an Infra layer implementation for a SwiftUI app:

// Infra/Datasources/ContentDatasource.swift
protocol ContentDatasource {
func fetchContents() -> [ContentModel]
}

// Infra/Models/ContentModel.swift
struct ContentModel {
var theme: String
var url: String
var level: String
}

// Infra/Repositories/ContentRepositoryImpl.swift
struct ContentRepositoryImpl: ContentRepository {
var datasource: ContentDatasource

func fetchContents() -> [ContentEntity] {
let contents: [ContentModel] = datasource.fetchContents()
return contents.map({ ContentMapper.toEntity(from: $0) })
}
}

// Infra/Mappers/ContentMapper.swift
struct ContentMapper {
static func toEntity(from content: ContentModel) -> ContentEntity {
return ContentEntity(theme: content.theme, url: content.url, level: content.level)
}

static func toModel(from content: ContentEntity) -> ContentModel {
return ContentModel(theme: content.theme, url: content.url, level: content.level)
}
}

External Layer

The External layer contains the concrete implementations of the datasources defined in the Infra layer. This layer can include specific implementations for local or remote access or integrations with external services.

The External layer is responsible for providing the necessary dependencies to the Infra layer, such as network clients, databases, or any other external services required for data retrieval and storage.

Presentation Layer

The Presentation layer is responsible for displaying data to the user and handling user interaction. It utilizes SwiftUI views for defining the appearance and interaction of the application.

In the Presentation layer, we typically have the following components:

  • ViewModel: A controller that manages the state and presentation logic of the data. ViewModels are responsible for fetching data from the UseCases in the Domain layer and transforming it into a format suitable for SwiftUI views.
  • UI: A folder that contains SwiftUI views. These views utilize the ViewModel to display data and handle user interactions.

Here’s an example of the Presentation layer implementation for a SwiftUI app:

// Presentation/ViewModels/ContentViewModel.swift
struct ContentViewModel {

private var _fetchContentsUseCase: FetchContentsUseCase

init(_ fetchContentsUseCase: FetchContentsUseCase) {
self._fetchContentsUseCase = fetchContentsUseCase
}

func fetchContents() -> [ContentEntity] {
return _fetchContentsUseCase.call()
}
}

// Presentation/UI/ContentView.swift
struct ContentView: View {

// MARK: Properties
@Dependency(\.features.contentFeature.contentViewModel) var viewModel

@State var contents: [ContentEntity] = []

// MARK: - Body
var body: some View {
HStack {
List {
ForEach(contents, id: \.url) { content in
Text(content.theme)
}
}
}
.task {
contents = viewModel.fetchContents()
}
}
}

Conclusion

Clean Architecture provides a solid foundation for building maintainable and scalable SwiftUI applications. By organizing the codebase into separate layers and defining clear boundaries between them, we achieve code that is modular, testable, and easier to maintain.

https://github.com/maykonmeneghel/Clean-SwiftUI

--

--