Clean Architecture: iOS Projektstruktur und Ebenen
Source: Dev.to
⚠️ Collection Error: Content refinement error: Error: 429 429 Too Many Requests: you (bkperio) have reached your weekly usage limit, upgrade for higher limits: https://ollama.com/upgrade
Clean Architecture: Projektstruktur und Ebenen
In einem professionellen iOS-Projekt ist die Ordnerstruktur deine erste Verteidigungslinie gegen „Spaghetti-Code“. Während die Kreise der Clean Architecture konzeptionell sind, muss deine Struktur im Xcode-Projekt oder im Swift Package (SPM) physisch vorhanden sein und strikt durchgesetzt werden.
Clean-Ebene Ordnername Inhalt Abhängigkeitsregel
Entities Domain/Entities Reine Swift-Structs (z. B. Translation).
Keine. Pures Swift.
Use Cases Domain/UseCases Logik-Protokolle und Interaktoren. Nur Entities.
Gateways Domain/Interfaces
Repository-Protokolle (z. B. TranslationRepository). Nur Entities.
Data Layer Data/Repositories Konkrete Repository-Implementierungen. Domain-Ebene.
Infrastructure Data/DataSources API-Clients, Core Data Manager, DTOs. Externe Frameworks (Alamofire, CoreData).
Presentation Presentation/UI SwiftUI Views, ViewModels, Coordinators. Domain-Ebene.
Für ein Projekt mit deiner Komplexität (Bibel-App, Core Data, mehrere Sprachen) ist ein modularisierter SPM-Ansatz ideal. Wenn du ein einzelnes Target nutzt, verwende diese Hierarchie: /App (Der Composition Root) ├── DependencyInjection/ (Swinject oder custom Container) └── AppLauncher.swift (App-Struktur)
/Domain (Das “Gehirn”) ├── Entities/ (Translation.swift, Verse.swift) ├── UseCases/ (FetchVersesUseCase.swift, BookmarkVerseUseCase.swift) └── Interfaces/ (TranslationRepositoryProtocol.swift)
/Data (The “Arbeiter”) ├── Repositories/ (TranslationRepository.swift) ├── DataSources/ │ ├── Remote/ (APIClient.swift, TranslationDTO.swift) │ └── Local/ (CoreDataStack.swift, CDTranslation+Mapping.swift) └── Mappers/ (DTOToEntityMapper.swift)
/Presentation (Das “Gesicht”) ├── Scenes/ (Die UI-Screens) │ ├── TranslationList/ │ │ ├── TranslationListView.swift │ │ └── TranslationListViewModel.swift │ └── TranslationDetail/ … ├── Components/ (Wiederverwendbare SwiftUI Views) └── Coordinator/ (Navigationslogik)
Die wichtigste Erkenntnis für einen Senior-Entwickler ist, dass Abhängigkeiten nur nach innen zeigen dürfen. Domain ist König: Sie weiß nichts über SwiftUI, Core Data oder Firebase. Wenn du deine Domain-Unit-Tests nicht ohne UIKit ausführen kannst, ist deine Architektur „undicht“ (Leaky Architecture). Die Repository-Brücke: Die Data-Ebene implementiert die in der Domain definierten Protokolle.
Domain: „Ich brauche einen Weg, um Übersetzungen zu erhalten.“ (Protokoll) Data: „Ich hole sie aus Core Data.“ (Implementierung) Die ViewModel-Brücke: Das ViewModel ruft den UseCase auf, der eine Entity zurückgibt. Das ViewModel konvertiert diese Entity dann in ein ViewItem (ein für die UI optimiertes Struct). In deiner App hat ein Core Data-Objekt oft Altlasten (Optionals, NSManagedObject-Logik). Mappers halten die Domain sauber. CoreDataTranslation (Data Layer): An Core Data gebunden, optionale Strings. Translation (Domain Layer): Sauber, nicht-optional, ein immutables Struct. Die Lösung: Erstelle in Data/Mappers eine einfache Extension: extension CoreDataTranslation{ func toDomain() -> Translation { // Mapping von DB-Modell zu sauberem Domain-Modell return Translation( id: self.id ?? UUID().uuidString, name: self.name ?? “Unbekannt” ) } }
Bei über 5 Jahren Erfahrung ist es ratsam, diese Ebenen in separate Swift Packages auszulagern: Domain Package: Null Abhängigkeiten. Extrem schnell zu testen. Data Package: Hängt nur von der Domain ab. UI Package: Hängt nur von der Domain ab. Der Vorteil: Es verhindert physisch, dass jemand versehentlich ein Datenbank-Modell in eine View importiert. Der Compiler würde den Dienst verweigern.