This project demonstrates the implementation of Clean Architecture principles in an iOS application using SwiftUI for the presentation layer. It provides a structured, maintainable, and testable codebase by separating concerns into distinct layers.
- Project Structure
- Architecture Overview
- Layers
- Dependency Injection
- Sample Feature
- Getting Started
- Best Practices
- SwiftUI vs UIKit Implementation
CleanArchitectureIOSAppSwiftUI/
├── Domain/ # Domain Layer
│ ├── Entities/ # Business models
│ ├── UseCases/ # Business logic
│ ├── Repositories/ # Repository interfaces
│ └── Protocols/ # Domain protocols and errors
├── Data/ # Data Layer
│ ├── Repositories/ # Repository implementations
│ ├── DataSources/ # Data sources
│ │ ├── Remote/ # Remote data sources (API)
│ │ └── Local/ # Local data sources (Database, UserDefaults)
│ └── Models/ # Data models (DTOs)
├── Presentation/ # Presentation Layer
│ ├── ViewModels/ # View models
│ ├── Views/ # SwiftUI views
│ └── UIModels/ # UI models
├── DI/ # Dependency Injection
│ ├── DIContainer.swift # DI container
│ └── ServiceLocator.swift # Service locator
└── CleanArchitectureApp.swift # Application entry point
This project follows the Clean Architecture principles introduced by Robert C. Martin (Uncle Bob). The architecture is divided into three main layers:
- Domain Layer: Contains business logic and rules, entities, use cases, and repository interfaces.
- Data Layer: Implements repository interfaces from the domain layer and handles data operations.
- Presentation Layer: Handles UI logic using SwiftUI, including view models and views.
The key principle is the dependency rule: source code dependencies only point inward. Inner layers don't know anything about outer layers.
The Domain Layer is the core of the application and contains:
- Entities: Business models that represent the core concepts of the application.
- Use Cases: Business logic that orchestrates the flow of data between entities and repositories.
- Repository Interfaces: Abstractions that define how to access data.
- Protocols: Common interfaces and error types used across the domain layer.
The Domain Layer is independent of any other layers and doesn't have any dependencies on frameworks or external libraries.
The Data Layer implements the repository interfaces defined in the Domain Layer and is responsible for:
- Repository Implementations: Concrete implementations of the repository interfaces.
- Data Sources: Components that handle data operations from different sources:
- Remote Data Sources: Handle API calls and network operations.
- Local Data Sources: Handle local storage operations (UserDefaults, CoreData, etc.).
- Data Models (DTOs): Data Transfer Objects that map between domain entities and external data formats.
The Presentation Layer handles the UI and user interactions using SwiftUI:
- View Models: Transform domain data into a format that can be easily displayed in the UI and provide reactive state management using Combine framework.
- Views: SwiftUI views that display data and handle user interactions.
- UI Models: Models specifically designed for UI representation, conforming to protocols like
Identifiablefor SwiftUI lists.
Key SwiftUI concepts used:
- @ObservedObject: To observe changes in view models
- @Published: To publish state changes from view models
- @State: For local view state management
- @Environment: To access environment values like dismiss actions
The project uses two approaches for dependency injection:
- DIContainer: A factory-based approach that creates and provides dependencies.
- ServiceLocator: A service locator pattern that allows resolving dependencies from anywhere in the app.
Both approaches help to maintain loose coupling between components and make the code more testable.
The project includes a sample User Management feature that demonstrates how all layers work together:
- Domain Layer: User entity, UserRepository interface, and use cases for user operations.
- Data Layer: UserRepositoryImpl, UserRemoteDataSource, and UserLocalDataSource.
- Presentation Layer: UserViewModel, UserListView, UserDetailView, and form views.
The feature allows:
- Viewing a list of users
- Viewing user details
- Adding new users
- Editing existing users
- Deleting users
To use this project as a template for your iOS application:
- Clone the repository
- Replace the sample User feature with your own domain entities and use cases
- Implement your own data sources and repositories
- Create your own view models and SwiftUI views
- Update the dependency injection setup as needed
- Single Responsibility Principle: Each class has only one reason to change.
- Dependency Inversion: High-level modules don't depend on low-level modules.
- Interface Segregation: Clients shouldn't depend on methods they don't use.
- Testability: The architecture makes it easy to write unit tests for each layer.
- Separation of Concerns: Each layer has its own responsibility.
- Loose Coupling: Components are loosely coupled through interfaces and dependency injection.
- SwiftUI Best Practices:
- Use
ObservableObjectand@Publishedfor reactive state management - Keep views small and composable
- Use preview providers for rapid UI development
- Use
This project demonstrates Clean Architecture with SwiftUI, which differs from a UIKit implementation in several ways:
-
Declarative vs Imperative UI:
- SwiftUI uses a declarative approach where you describe what the UI should look like
- UIKit uses an imperative approach where you manually manage view hierarchies
-
State Management:
- SwiftUI uses property wrappers like
@State,@ObservedObject, and@Published - UIKit typically uses delegation patterns and callbacks
- SwiftUI uses property wrappers like
-
View Lifecycle:
- SwiftUI has simpler lifecycle management with
onAppearandonDisappear - UIKit has more complex lifecycle methods like
viewDidLoad,viewWillAppear, etc.
- SwiftUI has simpler lifecycle management with
-
Navigation:
- SwiftUI uses
NavigationViewandNavigationLinkfor navigation - UIKit uses
UINavigationControllerand manual push/pop operations
- SwiftUI uses
-
Data Flow:
- SwiftUI has built-in reactive data flow with Combine framework integration
- UIKit requires manual implementation of reactive patterns or third-party libraries
Despite these differences, the core Clean Architecture principles remain the same, with the domain and data layers being identical between the two implementations.