iwantcoding.com
🔥 Daily 👥 Rooms 🏆 Top Log in Sign up

Swift Package Manager

Swift Package Manager is Apples bundler and dependency manager. Use it for library code, app modularisation, and resource bundles. Combined with `package.swift` manifests and Xcode integration, it has largely replaced CocoaPods for Swift projects.

Package manifest, dependencies, targets, modularisation

EXAMPLE
// 1) Create a package
swift package init --type library --name ShopKit
cd ShopKit
swift build
swift test

// 2) Package.swift
// swift-tools-version: 5.9
import PackageDescription

let package = Package(
    name: "ShopKit",
    platforms: [
        .iOS(.v15),
        .macOS(.v12),
        .tvOS(.v15),
    ],
    products: [
        .library(name: "ShopKit", targets: ["ShopKit"]),
        .library(name: "ShopKitTesting", targets: ["ShopKitTesting"]),
    ],
    dependencies: [
        .package(url: "https://github.com/apple/swift-collections.git", from: "1.0.0"),
        .package(url: "https://github.com/apple/swift-log.git", from: "1.5.0"),
    ],
    targets: [
        .target(
            name: "ShopKit",
            dependencies: [
                .product(name: "Collections", package: "swift-collections"),
                .product(name: "Logging",     package: "swift-log"),
            ],
            resources: [
                .process("Resources"),
            ]
        ),
        .target(
            name: "ShopKitTesting",
            dependencies: ["ShopKit"]
        ),
        .testTarget(
            name: "ShopKitTests",
            dependencies: ["ShopKit", "ShopKitTesting"]
        ),
    ]
)

// 3) Use the package from an app
// In Xcode: File -> Add Package Dependencies... -> paste URL
// OR in another Package.swift:
// .package(name: "ShopKit", path: "../ShopKit"),

// import ShopKit
// let cart = Cart()

// 4) Multi-target modularisation
// Split a large app into modules:
// - Network        wraps URLSession
// - Domain         business types
// - UI             SwiftUI views
// - App            top-level scene + composition root

// Each is a target in Package.swift; Xcode treats them as separate frameworks.
// Cyclic dependencies are impossible -> clean architecture by construction.

// 5) Resources
// Resources/Strings/Localizable.strings
// Resources/Images.xcassets
// Resources/Markdown/welcome.md
//
// .target(..., resources: [ .process("Resources") ])
//
// Bundle.module gives access at runtime:
// let url = Bundle.module.url(forResource: "welcome", withExtension: "md")

// 6) Localised strings
// String(localized: "hello.world", bundle: .module)

// 7) Tests
// Tests/ShopKitTests/CartTests.swift
import XCTest
@testable import ShopKit

final class CartTests: XCTestCase {
    func test_add_increases_count() {
        let c = Cart()
        c.add(Item(sku: "sku-1", price: 4995))
        XCTAssertEqual(c.itemCount, 1)
    }
}

// 8) Versioning + publishing
// git tag 1.0.0 + git push origin 1.0.0
// Consumers pin: .package(url: "...", from: "1.0.0")
// .upToNextMajor / .upToNextMinor / .exact for finer control

// 9) Binary frameworks (XCFramework)
// .binaryTarget(name: "Stripe", url: "...", checksum: "...")
// Useful for closed-source SDKs

// 10) Patterns to internalise
// - One module per concern; explicit dependencies between them
// - Bundle.module for any package-owned resource
// - Tests as their own target
// - .resources: [.process(...)] for assets to be included in the package
// - Version manifests with explicit platforms

// 11) Pitfalls
// - Cyclic dependencies (SPM refuses; refactor)
// - Forgetting Bundle.module -> 'resource not found' at runtime
// - Hardcoded URLs in resources (use bundle URLs)
// - Importing UIKit in a multi-platform package (guard with #if canImport)
// - Tagging without changing the version in your manifest

// 12) Modern app structure
// MyApp/
// ├── App.swift                        @main + composition root
// ├── Package.swift                    if your app is a SPM package
// ├── Sources/
// │   ├── App/                          imports Domain + Network + UI
// │   ├── Domain/
// │   ├── Network/
// │   └── UI/
// └── Tests/

Why it matters

Swift Package Manager + modular targets is the modern Swift project shape. Split by concern (Domain, Network, UI), let SPM refuse cyclic dependencies, and your app gets clean architecture as a side effect of getting the build to compile. Xcode 15+ handles it all; CocoaPods is rarely the right call for new projects.

Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.

Example

Example
// Package.swift
let package = Package(
    name: "MyLib",
    products: [.library(name: "MyLib", targets: ["MyLib"])],
    dependencies: [.package(url: "https://github.com/apple/swift-collections", from: "1.0.0")],
    targets: [.target(name: "MyLib", dependencies: [.product(name: "Collections", package: "swift-collections")])]
)
Try it Yourself »

Discussion

Loading…