SPM (Swift Package Manager) is a built-in package manager for the Swift ecosystem, developed by Apple to automate connecting, building and updating third-party libraries. SPM has been part of the Swift compiler since version 3.0 (2016) and requires no separate installation. Unlike CocoaPods and Carthage, SPM integrates directly with the compiler and Xcode, making it the standard dependency management tool in modern Swift projects. In this article we'll explore the structure of Package.swift, SPM commands, creating custom packages and migrating from alternative managers.
Key Takeaways
SPM (Swift Package Manager) is the official package manager for the Swift language, built into the swiftc compiler and the Xcode development environment. It allows developers to add third-party libraries, manage their versions and publish their own packages. SPM first appeared in Swift 3.0 (September 2016) as a command-line tool, and starting with Xcode 11 (2019) it received full integration with the graphical interface — dependencies are now added through the File → Add Packages menu.
SPM automatically downloads dependency source code from Git repositories, builds them in parallel with the main project and caches the results so that subsequent builds execute faster. Unlike CocoaPods, SPM does not generate a separate workspace (xcworkspace) — dependencies become part of the main Xcode project. According to the Swift.org Developer Survey (2024), 67% of iOS developers use SPM, making it the most popular dependency management tool in the Swift ecosystem.
SPM supports three platforms: Apple (iOS, macOS, tvOS, watchOS, visionOS), Linux (Ubuntu, CentOS, Amazon Linux) and server-side Swift (Vapor, Kitura). On Linux, SPM works entirely through the command line without Xcode.
SPM is built around three key concepts: packages, products and targets. A package is a Git repository with a Package.swift manifest. A product is the build result (a library or an executable). A target is a module inside the package that compiles into a build unit.
When a developer adds a dependency to Package.swift, SPM performs the following steps:
~Library/Caches/org.swift.swiftpm/.The Package.resolved file locks the exact versions of all dependencies so that the development team works with an identical set of libraries. This file should be committed to version control (git).
A key advantage of SPM over alternatives is the absence of a centralized registry. Packages can reside in any public Git repository: GitHub, GitLab, Bitbucket, as well as on company private Git servers. Since Swift 5.2, SPM supports binary dependencies (binary targets) — closed-source libraries distributed as XCFramework without providing source code.
Package.swift is a Swift file that describes the package structure and its dependencies. The file is written in Swift itself (not JSON, not YAML), which allows using conditional logic, computed constants and functions inside the manifest.
Basic Package.swift structure:
// swift-tools-version: 5.9
import PackageDescription
let package = Package(
name: "MyLibrary",
platforms: [
.iOS(.v16),
.macOS(.v13)
],
products: [
.library(
name: "MyLibrary",
targets: ["MyLibrary"]
),
],
dependencies: [
.package(url: "https://github.com/Alamofire/Alamofire.git",
from: "5.9.0"),
.package(url: "https://github.com/onevcat/Kingfisher.git",
from: "7.12.0"),
],
targets: [
.target(
name: "MyLibrary",
dependencies: [
"Alamofire",
"Kingfisher"
]
),
.testTarget(
name: "MyLibraryTests",
dependencies: ["MyLibrary"]
),
]
)
Let's break down the key elements:
// swift-tools-version: 5.9 — a directive specifying the SPM version; the available manifest syntax depends on it.name — the package name, displayed in Xcode and used in dependency links.platforms — minimum platform versions; SPM will not allow building the package on an older OS version.products — what the package "exports": a library (.library) or an executable (.executable).dependencies — a list of external packages with URL and version; supports from:, exact:, branch:, revision:.targets — build targets; each target contains a list of dependencies, resources and swift files from the corresponding directory (Sources/TargetName/).Example of specifying an exact version, branch and commit:
dependencies: [
.package(url: "https://github.com/pointfreeco/swift-snapshot-testing.git",
exact: "1.17.3"),
.package(url: "https://github.com/pointfreeco/swift-composable-architecture.git",
branch: "main"),
.package(url: "https://github.com/apple/swift-log.git",
revision: "e5c6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4"),
]
Since Swift 5.9, Package.swift has added support for static framework and linkerSettings, allowing more precise linker configuration for static and dynamic libraries.
Swift Package Manager provides a set of commands for working through the terminal. Commands are run from the package root directory (where Package.swift is located).
# Create a new package with a library
swift package init --type library
# Create an executable package (console application)
swift package init --type executable
# Build the project
swift build
# Build in release configuration
swift build -c release
# Run tests
swift test
# Run a specific test
swift test --filter "MyLibraryTests/testExample"
# Download and resolve dependencies
swift package resolve
# Update dependencies to the latest available versions
swift package update
# Show dependency graph
swift package show-dependencies
# Clean build cache
swift package clean
# Generate Xcode project (before Xcode 11)
swift package generate-xcodeproj
When working inside Xcode, most of these commands run automatically: dependencies are resolved when opening the project, building starts with ⌘B, tests with ⌘U. However, knowing terminal commands is necessary for CI/CD pipelines (GitHub Actions, GitLab CI, Jenkins) where Xcode is not available.
The swift package resolve command creates or updates the Package.resolved file. This file locks the exact versions of all dependencies, including transitive ones, and should be committed to git. It is recommended to run swift package update before each new feature branch to work with up-to-date library versions.
Creating your own SPM package is useful for encapsulating business logic in multi-module projects and for publishing open-source libraries. Let's go through the step-by-step process.
mkdir MyNetworkKit
cd MyNetworkKit
swift package init --type library
The swift package init command creates the following structure:
MyNetworkKit/
├── Package.swift
├── README.md
├── Sources/
│ └── MyNetworkKit/
│ └── MyNetworkKit.swift
└── Tests/
└── MyNetworkKitTests/
└── MyNetworkKitTests.swift
SPM automatically scans the Sources/ and Tests/ directories: each subdirectory inside Sources corresponds to a target.
Let's add dependencies and configure target platforms:
// swift-tools-version: 5.9
import PackageDescription
let package = Package(
name: "MyNetworkKit",
platforms: [
.iOS(.v15),
.macOS(.v12)
],
products: [
.library(
name: "MyNetworkKit",
targets: ["MyNetworkKit"]
),
],
dependencies: [
.package(url: "https://github.com/Alamofire/Alamofire.git",
from: "5.9.0"),
],
targets: [
.target(
name: "MyNetworkKit",
dependencies: ["Alamofire"]
),
.testTarget(
name: "MyNetworkKitTests",
dependencies: ["MyNetworkKit"]
),
]
)
// Sources/MyNetworkKit/MyNetworkKit.swift
import Foundation
import Alamofire
public struct NetworkClient {
private let session: Session
public init() {
let configuration = URLSessionConfiguration.default
configuration.timeoutIntervalForRequest = 30
self.session = Session(configuration: configuration)
}
public func fetchData(from url: String) async throws -> Data {
let response = try await session.request(url).serializingData().value
return response
}
}
Push the package to a Git repository and create a SemVer tag:
git init
git add .
git commit -m "Initial commit: MyNetworkKit"
git remote add origin https://github.com/username/MyNetworkKit.git
git push -u origin main
git tag 1.0.0
git push --tags
After this, any developer can add your package using .package(url: "https://github.com/username/MyNetworkKit.git", from: "1.0.0").
Alamofire is the most popular HTTP client for Swift. Let's add it via SPM and make a GET request.
import Alamofire
func fetchUsers() {
AF.request("https://jsonplaceholder.typicode.com/users")
.validate()
.responseDecodable(of: [User].self) { response in
switch response.result {
case .success(let users):
print("Received (users.count) users")
case .failure(let error):
print("Error: (error.localizedDescription)")
}
}
}
The Swinject library provides a DI container for Swift. It is added via .package(url: "https://github.com/Swinject/Swinject.git", from: "2.8.0").
import Swinject
let container = Container()
container.register(NetworkServiceProtocol.self) { _ in NetworkService() }
container.register(DataRepositoryProtocol.self) { r in
DataRepository(networkService: r.resolve(NetworkServiceProtocol.self)!)
}
let repository = container.resolve(DataRepositoryProtocol.self)
repository?.loadData()
The swift-log package from Apple provides a unified logging API supporting multiple backends (OSLog, console, files).
import Logging
var logger = Logger(label: "com.myapp.network")
logger.logLevel = .debug
logger.info("Network request started", metadata: [
"url": "(requestURL)",
"method": "GET"
])
logger.warning("Response time exceeded 2 seconds")
logger.error("Connection error: no internet")
These three examples cover typical SPM usage scenarios: HTTP clients, DI containers and system infrastructure. The library selection is not accidental — Alamofire, Swinject and swift-log are among the top 20 most starred Swift packages on GitHub.
If your project uses CocoaPods or Carthage, migration to SPM is done in a few steps. The process is safe: SPM dependencies can coexist with CocoaPods and Carthage in the same project, allowing gradual migration.
.xcworkspace, open the .xcodeproj and perform Clean Build Folder.rm -rf Carthage/ in the terminal.As of 2025, SPM supports the vast majority of popular Swift libraries. Exceptions are some ObjC frameworks without module maps. If a library does not yet support SPM — check the Installation section in its README; most authors have already added SPM support in the latest versions.
Frequently Asked Questions
SPM is built into the Swift compiler and Xcode, requiring no installation via gem or Homebrew. CocoaPods uses a centralized Specs registry and generates a separate workspace. Carthage works through frameworks without project integration. SPM is the only manager integrated at the compiler level: dependencies are resolved, cached and built in parallel with the main code.
Yes, SPM supports mixed Swift + Objective-C projects. ObjC files inside an SPM package automatically end up in an Umbrella Header provided a correct modulemap exists. However, SPM does not support static ObjC libraries that lack a module map. It is recommended to connect ObjC libraries via SPM only if they provide a modulemap or are written in pure C.
SPM uses semantic versioning (SemVer). If package A requires Alamofire 5.8+, and package B requires Alamofire 5.9+, SPM will select version 5.9.x satisfying both. If the conflict is unresolvable (one package requires 5.x, another requires 6.x), SPM will report an error. In that case you need to update one of the packages or switch the dependency to a version compatible with both requirements.
On macOS: ~Library/Caches/org.swift.swiftpm/ and ~/Library/Developer/Xcode/DerivedData/. On Linux: ~cache/swiftpm/. During builds, SPM caches source code and compiled object files. To fully clean the cache, run swift package reset — this command removes the dependency cache and DerivedData for the current project.
Yes, since Swift 5.2 SPM supports binary targets. A closed-source library is distributed as an XCFramework, and the path to the .xcframework is specified in Package.swift. The source code is not exposed. A binary target is specified via .binaryTarget(name: "PrivateSDK", path: "Sources/PrivateSDK.xcframework"). This allows connecting commercial SDKs without violating license agreements.
Summary
We will develop a mobile application turnkey
IT Sectr creates iOS and Android applications for startups and businesses since 2017. We will advise you and propose the best solution.
Read also