Skip to content

Swift Package Manager — Creating, Publishing, and Managing Packages

DodaTech Updated 2026-06-28 6 min read

In this tutorial, you will learn about Swift Package Manager. We cover key concepts, practical examples, and best practices to help you master this topic.

Swift Package Manager (SPM) is Apple's official build system and dependency manager integrated into Swift, Xcode, and server-side Swift frameworks like Vapor for distributing reusable libraries.

What You'll Learn

  • Package.swift structure and configuration
  • Creating and building Swift packages
  • Adding and managing dependencies
  • Publishing packages on GitHub
  • Versioning with semantic versioning
  • Using SPM in iOS and macOS apps

Why It Matters

SPM eliminates the need for third-party dependency managers like CocoaPods and Carthage for most projects. It is integrated into Xcode, supports binary frameworks, and is the standard way to distribute Swift libraries. The Swift community publishes thousands of packages on SPM.

Real-World Use

A team developing a social media app uses SPM to pull in Alamofire for networking, SwiftyJSON for JSON parsing, and Kingfisher for image Caching. They also maintain an internal "AppCore" package shared across multiple apps. Adding a new dependency is a single line in Package.swift.

Learning Path

flowchart LR
  A[Testing
Lesson 31] --> B[Swift Package Manager
You are here] B --> C[Project: Todo App
Lesson 33] B --> D[Project: Networking Library
Lesson 35] style B fill:#f90,color:#fff

Creating a Package

Create a Swift package from the command line or Xcode.

# Command line
mkdir MyLibrary
cd MyLibrary
swift package init --name MyLibrary --type library

This creates:

MyLibrary/
  Package.swift
  Sources/
    MyLibrary/
      MyLibrary.swift
  Tests/
    MyLibraryTests/
      MyLibraryTests.swift

The generated Package.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: [],
    targets: [
        .target(
            name: "MyLibrary"
        ),
        .testTarget(
            name: "MyLibraryTests",
            dependencies: ["MyLibrary"]
        ),
    ]
)

Building and Testing

# Build the package
swift build

# Run tests
swift test

# Build for release
swift build -c release

# Generate Xcode project
swift package generate-xcodeproj

# Show dependency graph
swift package show-dependencies

Adding Dependencies

// swift-tools-version: 5.9
import PackageDescription

let package = Package(
    name: "WeatherKit",
    platforms: [.macOS(.v13), .iOS(.v16)],
    products: [
        .library(name: "WeatherKit", targets: ["WeatherKit"])
    ],
    dependencies: [
        .package(url: "https://github.com/Alamofire/Alamofire.git",
                 from: "5.8.0"),
        .package(url: "https://github.com/onevcat/Kingfisher.git",
                 from: "7.10.0"),
        .package(path: "../SharedUtilities"),
    ],
    targets: [
        .target(
            name: "WeatherKit",
            dependencies: [
                "Alamofire",
                .product(name: "Kingfisher", package: "Kingfisher"),
                "SharedUtilities"
            ]
        ),
        .testTarget(
            name: "WeatherKitTests",
            dependencies: ["WeatherKit"]
        ),
    ]
)

Creating a Library

A sample library with multiple source files.

// Sources/WeatherKit/WeatherService.swift
import Foundation

public struct WeatherService {
    private let apiKey: String

    public init(apiKey: String) {
        self.apiKey = apiKey
    }

    public func fetchWeather(for city: String) async throws -> Weather {
        return Weather(temperature: 22.0, condition: "Sunny")
    }
}

// Sources/WeatherKit/Weather.swift
public struct Weather: Codable, Sendable {
    public let temperature: Double
    public let condition: String

    public init(temperature: Double, condition: String) {
        self.temperature = temperature
        self.condition = condition
    }
}

Types that should be accessible to package consumers must be marked public or open.

Package Products

A package can produce multiple products.

let package = Package(
    name: "Utilities",
    products: [
        .library(name: "CoreUtils", targets: ["CoreUtils"]),
        .library(name: "NetworkingUtils", targets: ["NetworkingUtils"]),
        .library(
            name: "AllUtils",
            type: .dynamic,
            targets: ["CoreUtils", "NetworkingUtils"]
        ),
        .executable(name: "CLITool", targets: ["CLITool"]),
    ],
    targets: [
        .target(name: "CoreUtils"),
        .target(name: "NetworkingUtils", dependencies: ["CoreUtils"]),
        .executableTarget(name: "CLITool", dependencies: ["CoreUtils"]),
    ]
)

Consumers choose which products to import, avoiding pulling in unnecessary dependencies.

Versioning

SPM uses semantic versioning.

// Exact version
.package(url: "...", exact: "1.2.3")

// Up to next major (1.x.x, excluding 2.0.0)
.package(url: "...", from: "1.2.3")

// Up to next minor (1.2.x, excluding 1.3.0)
.package(url: "...", .upToNextMinor(from: "1.2.3"))

// Range
.package(url: "...", "1.0.0"..<"2.0.0")

// Branch
.package(url: "...", branch: "main")

// Commit
.package(url: "...", revision: "a1b2c3d4")

For published packages, use from: or upToNextMinor:. Branch and commit dependencies are for development only.

Publishing a Package

Steps to publish your package:

# 1. Tag a version
git tag 1.0.0
git push --tags

# 2. Verify the package
swift package diagnose-api-breaking-changes 1.0.0

# 3. Create a GitHub release
gh release create 1.0.0 --notes "Initial release"

SPM resolves package URLs via GitHub tags. Users add https://github.com/yourname/YourPackage.git to their dependencies.

Resource Bundles

Packages can include resources like images, xibs, and asset catalogs.

let package = Package(
    name: "MyUIComponents",
    targets: [
        .target(
            name: "MyUIComponents",
            resources: [
                .process("Assets.xcassets"),
                .copy("Fonts"),
                .process("Nibs"),
            ]
        ),
    ]
)

.Process applies platform-specific processing (compiling asset catalogs). .copy copies files as-is.

Binary Frameworks

SPM supports binary dependencies for closed-source libraries.

let package = Package(
    name: "AnalyticsSDK",
    products: [
        .library(name: "AnalyticsSDK", targets: ["AnalyticsSDK"])
    ],
    targets: [
        .binaryTarget(
            name: "AnalyticsSDK",
            path: "artifacts/AnalyticsSDK.xcframework"
        ),
        // Or from a URL:
        // .binaryTarget(
        //     name: "AnalyticsSDK",
        //     url: "https://example.com/AnalyticsSDK-1.0.0.xcframework.zip",
        //     checksum: "..."
        // ),
    ]
)

Use swift package compute-checksum to generate the checksum for remote binary targets.

Common Mistakes

  1. Not marking types as public: Types not marked public are internal to the package and invisible to consumers.

  2. Missing platform versions: Always specify minimum platform versions for packages that use platform-specific APIs.

  3. Using branch dependencies in production: Branch dependencies break version resolution. Use tagged versions for releases.

  4. Not running API breaking checks before release: Use swift package diagnose-api-breaking-changes to catch accidental breaking changes.

  5. Forgetting to commit Package.resolved: The resolved file pins dependency versions for reproducible builds.

Practice Questions

  1. What is the difference between a library and an executable in SPM?
  2. How do you specify version constraints for dependencies?
  3. Why must you mark public API with the public keyword in a package?
  4. How do you include image resources in a Swift package?
  5. Challenge: Create a Swift package called "StringUtilities" with two targets: "Core" (string validation methods) and "Extended" (encryption helpers). The Extended target depends on Core. Publish it to a local Git Repository with version 1.0.0.

Mini Project

Create and publish a SwiftCache package that:

  • Provides a generic Cache<Key: Hashable, Value> type
  • Supports in-memory and disk-based storage
  • Has configurable TTL (time-to-live) and max size
  • Includes resource files for default configuration
  • Has 80%+ test coverage
  • Is published to GitHub with proper versioning and release notes

FAQ

Should I use SPM or CocoaPods?

SPM is the recommended dependency manager for new projects. It is built into Swift and Xcode, faster, and avoids the CocoaPods Ruby dependency. Use CocoaPods only for libraries that do not support SPM.

Can I use SPM with UIKit apps?

Yes. Xcode supports adding SPM packages to any project target — iOS, macOS, watchOS, tvOS.

How do I update dependencies?

Run swift package update or use Xcode's File menu (Packages > Update to Latest). Both update to the latest allowed version.

What is Package.resolved?

It locks all dependency versions for reproducible builds. Commit it to your repository so everyone builds with the same versions.

Can SPM depend on iOS-only frameworks like UIKit?

Yes. Use platform checks in your code: #if canImport(UIKit) or conditional target dependencies.

What's Next

Apply your SPM knowledge by building the Project: Todo App or the Project: Networking Library as a reusable SPM package.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro