Clean Architecture Setup for iOS App

NOVASOLUTIONS.TECHNOLOGY is engaged in the development, support and maintenance of iOS, Android, PWA mobile applications. We have extensive experience and expertise in publishing mobile applications in popular markets like Google Play, App Store, Amazon, AppGallery and others.
Development and support of all types of mobile applications:
Information and entertainment mobile applications
News apps, games, reference guides, online catalogs, weather apps, fitness and health apps, travel apps, educational apps, social networks and messengers, quizzes, blogs and podcasts, forums, aggregators
E-commerce mobile applications
Online stores, B2B apps, marketplaces, online exchanges, cashback services, exchanges, dropshipping platforms, loyalty programs, food and goods delivery, payment systems.
Business process management mobile applications
CRM systems, ERP systems, project management, sales team tools, financial management, production management, logistics and delivery management, HR management, data monitoring systems
Electronic services mobile applications
Classified ads platforms, online schools, online cinemas, electronic service platforms, cashback platforms, video hosting, thematic portals, online booking and scheduling platforms, online trading platforms

These are just some of the types of mobile applications we work with, and each of them may have its own specific features and functionality, tailored to the specific needs and goals of the client.

Showing 1 of 1 servicesAll 1735 services
Clean Architecture Setup for iOS App
Complex
~3-5 business days
FAQ
Our competencies:
Development stages
Latest works
  • image_mobile-applications_feedme_467_0.webp
    Development of a mobile application for FEEDME
    756
  • image_mobile-applications_xoomer_471_0.webp
    Development of a mobile application for XOOMER
    624
  • image_mobile-applications_rhl_428_0.webp
    Development of a mobile application for RHL
    1052
  • image_mobile-applications_zippy_411_0.webp
    Development of a mobile application for ZIPPY
    947
  • image_mobile-applications_affhome_429_0.webp
    Development of a mobile application for Affhome
    862
  • image_mobile-applications_flavors_409_0.webp
    Development of a mobile application for the FLAVORS company
    445

Clean Architecture Setup for iOS App

When iOS app codebase grows to 50–70 screens, ViewController answers for both network requests and data transformation and navigation. Tests have nowhere to go — dependencies hardcoded. New developer opens ProfileViewController.swift at 1200 lines and closes laptop.

Clean Architecture solves this through separation into concentric layers with strict dependency direction: inner layers know nothing about outer ones.

How Clean Architecture works in practice in iOS project

In Bob Martin's classical interpretation three rings: EntitiesUse CasesInterface Adapters. In iOS this maps as follows.

Domain layer — core. Here Entity models: clean Swift structs without Foundation import, only business data. Nearby — UseCase protocols and implementations. For example, FetchUserProfileUseCase takes UserRepository via DI and returns AnyPublisher<UserProfile, DomainError>. No URLSession, no CoreData. This layer compiles and tests in isolation.

protocol UserRepository {
    func fetchProfile(id: String) -> AnyPublisher<UserProfile, DomainError>
}

final class FetchUserProfileUseCase {
    private let repository: UserRepository
    init(repository: UserRepository) { self.repository = repository }

    func execute(id: String) -> AnyPublisher<UserProfile, DomainError> {
        repository.fetchProfile(id: id)
    }
}

Data layer — repository implementations. UserRepositoryImpl works with URLSession or Alamofire, maps DTO → domain model, handles network errors. CoreDataUserCache implements same protocol for local cache. Data source choice — in UserRepositoryImpl via strategy or in DI container.

Presentation layer — here ViewModel/Presenter lives. With SwiftUI convenient ObservableObject-ViewModel: calls UseCase, transforms result into @Published state and publishes it. ViewController or SwiftUI View handles exclusively rendering.

Navigation: Coordinator or Router

Typical problem — ViewController creates next ViewController and does push. This violates Clean Architecture: presentation layer knows concrete types of other screens. Solution — Coordinator:

protocol ProfileCoordinator: AnyObject {
    func showEditProfile(user: UserProfile)
    func showOrders(userId: String)
}

ViewModel holds weak reference to ProfileCoordinator. Concrete ProfileCoordinatorImpl knows UINavigationController and next screens. ViewModel — no.

DI: pure injection without Service Locator

Service Locator (global DIContainer.shared.resolve()) — anti-pattern: hides dependencies and breaks tests. Use initializer injection in chain: SceneDelegate creates AppCoordinator, that — concrete repositories and UseCases, passes to ViewModel via init. Can integrate Swinject or Needle, but for most projects manual assembly in CompositionRoot sufficient.

Testability — main win

Domain layer tested via XCTest without UIKit dependency: create MockUserRepository, put in UseCase, check logic. No XCTestExpectation for network, no URLSession mocks.

final class FetchUserProfileUseCaseTests: XCTestCase {
    func test_execute_returnsProfile() {
        let mock = MockUserRepository(result: .success(.stub()))
        let sut = FetchUserProfileUseCase(repository: mock)
        var received: UserProfile?
        _ = sut.execute(id: "123").sink(
            receiveCompletion: { _ in },
            receiveValue: { received = $0 }
        )
        XCTAssertEqual(received?.id, "123")
    }
}

Domain layer test build time — seconds, not minutes. This changes team development culture.

Typical implementation mistakes

Too thin UseCases. GetUsernameUseCase that does return user.name — meaningless layer. UseCase justified when encapsulating non-trivial logic or orchestrating multiple repositories.

Domain models with Codable. Adding Codable to domain Entity means Data layer leak inside. DTO — in Data layer, mapping — there too.

ViewModel knows concrete repository. If ViewModel has let repo = UserRepositoryImpl(...) — no DI. Only protocol + initializer injection.

What's included in setup

Audit current architecture (if project exists): determine what goes to Domain, what stays in Presentation, which dependencies to invert.

Create base module structure: Domain, Data, Presentation — separate Swift Packages or targets in one Xcode project. Set up target dependencies: Data depends on Domain, Presentation depends on Domain, not Data.

Implement CompositionRoot / DI container. Set up first 2–3 feature modules as example for team.

Write base domain layer tests as example.

Timelines

Architecture setup from scratch on new project (structure + DI + first module): 3–5 days. Refactoring existing project migrating 10–15 modules: 2–4 weeks depending on volume. Cost calculated after analyzing current code and architecture.