SPM: what it is, Swift Package Manager and Package.swift

Author: IT Sectr Published: 2026-02-13 Reading time: 11 min

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 a package manager built into the Swift compiler that requires no separate installation; it works on iOS, macOS, Linux and server platforms.
  • Package.swift is a manifest file that describes the package name, platforms, dependencies and targets in a declarative format.
  • SPM resolves dependencies using semantic versioning (SemVer), caches source code and builds packages in parallel for faster performance.
  • Commands: swift package init (create a package), swift package update (update dependencies), swift build (compile), swift test (run tests).
  • Migration from CocoaPods/Carthage to SPM is done through Xcode: File → Add Package Dependency, after which the podfile and Cartfile are removed.

What is SPM?

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.

How Swift Package Manager works

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:

  1. Cloning — SPM downloads the dependency's Git repository from the specified URL.
  2. Version resolution — it analyzes SemVer tags (e.g. 2.1.3) and selects the appropriate version within the specified range.
  3. Transitive resolution — it checks the dependencies of dependencies and builds a conflict-free version graph.
  4. Caching — it saves the downloaded source code in ~Library/Caches/org.swift.swiftpm/.
  5. Compilation — it builds all package targets with the main project's flags.

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 — project manifest

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
// 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:

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

Basic SPM commands

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

bash
# 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 package

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.

Step 1: Initialization

bash
mkdir MyNetworkKit
cd MyNetworkKit
swift package init --type library

Step 2: Directory structure

The swift package init command creates the following structure:

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

Step 3: Editing Package.swift

Let's add dependencies and configure target platforms:

swift
// 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"]
        ),
    ]
)

Step 4: Writing code

swift
// 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
    }
}

Step 5: Publishing

Push the package to a Git repository and create a SemVer tag:

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

SPM usage examples

Example 1: Adding Alamofire for network requests

Alamofire is the most popular HTTP client for Swift. Let's add it via SPM and make a GET request.

swift
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)")
            }
        }
}

Example 2: Swinject — dependency injection

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

swift
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()

Example 3: Swift-log for structured logging

The swift-log package from Apple provides a unified logging API supporting multiple backends (OSLog, console, files).

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

Migration from CocoaPods and Carthage

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.

CocoaPods → SPM

  1. In Xcode: File → Add Package Dependency, enter the package URL.
  2. Select the version and add the package to the required targets.
  3. After adding all dependencies via SPM, remove the lines from Podfile.
  4. Remove the .xcworkspace, open the .xcodeproj and perform Clean Build Folder.

Carthage → SPM

  1. Add packages via Xcode File → Add Package Dependency.
  2. Remove dependencies from Cartfile.
  3. Remove Carthage build scripts from Build Phases.
  4. Clear the cache: 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

How is SPM different from CocoaPods and Carthage?

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.

Can SPM be used for Objective-C projects?

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.

How does SPM resolve version conflicts?

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.

Where are downloaded SPM packages stored?

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.

Does SPM support closed-source (proprietary) libraries?

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

  • SPM (Swift Package Manager) is a built-in Swift package manager that requires no separate installation and is integrated with Xcode and the compiler.
  • Package.swift is a declarative manifest written in Swift, describing the package name, platforms, dependencies, products and build targets.
  • SPM uses Git repositories as package sources and resolves versions via SemVer, caching source code for faster subsequent builds.
  • Main commands: swift package init (create a package), swift build (compile), swift test (test), swift package update (update dependencies).
  • A custom package is created via swift package init, published to Git and made available to other projects via URL with a SemVer tag.
  • Migration from CocoaPods/Carthage to SPM is safe: dependencies can coexist, migration is done via File → Add Package Dependency in Xcode.
  • SPM is the standard dependency management tool in the Swift ecosystem, used by 67% of iOS developers (Swift.org Developer Survey, 2024).

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.

Discuss the project

Read also