DEV Community

Van Hung Nguyen
Van Hung Nguyen

Posted on

Clean Architecture: iOS Projektstruktur und Ebenen

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.


1. Das Mapping: Theorie vs. Ordner

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.

2. Die empfohlene Projektstruktur

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)
Enter fullscreen mode Exit fullscreen mode

3. Der Abhängigkeitsfluss (Die "Rule of Inward")

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).

4. Warum Mappers nutzen? (Data vs. Domain)

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"
        )
    }
}
Enter fullscreen mode Exit fullscreen mode

5. Senior-Strategie: Modularisierung (SPM)

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.

Top comments (0)