Monorepo with Swift: Code Organization in Practice

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

Monorepo with Swift: Code Organization in Practice



In modern iOS and macOS development, choosing a code management strategy is one of the key factors affecting scalability, delivery speed, and product quality. Traditionally, developers used multi-repositories (polyrepo), where each module or service lives in a separate Git repository. However, as projects and teams grow, the question increasingly arises: how to organize code so that it remains cohesive, reusable, and easily maintainable? The answer is a monorepo with Swift. In this article, we will explore what a monorepo is, why it is suitable for Swift projects, and how to organize it in practice using Swift Package Manager (SPM).



What is a Monorepo and Why is it Needed in Swift?



A monorepo is an approach where all the code of a project (or several related projects) is stored in a single repository. Unlike polyrepo, where each component is isolated, a monorepo unifies libraries, applications, tests, configurations, and documentation under a single version control.



For Swift developers, a monorepo is particularly relevant for several reasons:

  • Single version of dependencies: You avoid version mismatch of libraries between different applications.
  • Simplified refactoring: Changes to a shared module are immediately visible to all consumers within the repository.
  • Fast integration: New features can be tested and released in a single pull request.
  • Compatibility with SPM: Swift Package Manager is ideal for monorepos as it supports local packages and relative paths.


Monorepo Structure for a Swift Project



Let's consider a practical example. Suppose we have an iOS app (using SwiftUI) and a macOS app (using AppKit) that share common modules: a networking layer, authentication logic, and UI components. Let's organize the monorepo as follows:



MyMonorepo/├── Package.swift├── Sources/│   ├── AppiOS/│   │   └── main.swift│   ├── AppMacOS/│   │   └── main.swift│   ├── CoreNetworking/│   │   ├── Sources/│   │   │   └── HTTPClient.swift│   │   └── Tests/│   ├── CoreAuth/│   │   ├── Sources/│   │   │   └── AuthService.swift│   │   └── Tests/│   └── UIComponents/│       ├── Sources/│       │   └── CustomButton.swift│       └── Tests/├── Tests/│   ├── AppiOSTests/│   └── AppMacOSTests/├── .gitignore└── README.md


The key file is Package.swift. It describes all modules and their dependencies. An example of such a file:



// swift-tools-version:5.9import PackageDescription

let package = Package( name: "MyMonorepo", platforms: [ .iOS(.v17), .macOS(.v14) ], products: [ .executable(name: "AppiOS", targets: ["AppiOS"]), .executable(name: "AppMacOS", targets: ["AppMacOS"]), .library(name: "CoreNetworking", targets: ["CoreNetworking"]), .library(name: "CoreAuth", targets: ["CoreAuth"]), .library(name: "UIComponents", targets: ["UIComponents"]) ], dependencies: [ // External dependencies, if needed // .package(url: "https://github.com/...", from: "1.0.0") ], targets: [ .executableTarget( name: "AppiOS", dependencies: ["CoreNetworking", "CoreAuth", "UIComponents"], path: "Sources/AppiOS" ), .executableTarget( name: "AppMacOS", dependencies: ["CoreNetworking", "CoreAuth", "UIComponents"], path: "Sources/AppMacOS" ), .target( name: "CoreNetworking", dependencies: [], path: "Sources/CoreNetworking/Sources" ), .target( name: "CoreAuth",

Blogs

Book Recommendations