lang-swift-library-dev

aRustyDev's avatarfrom aRustyDev

Swift package development with SPM. Use for Package.swift, public APIs, DocC, XCTest, XCFrameworks, and library publishing. Extends meta-library-dev.

⭐1stars🔀1forks📁View on GitHub🕐Updated Jan 4, 2026

When & Why to Use This Skill

This Claude skill provides expert guidance on Swift package development using Swift Package Manager (SPM). It covers the entire lifecycle of library creation, including Package.swift configuration, public API design patterns, DocC documentation, XCTest implementation, and XCFramework publishing to ensure high-quality, maintainable Swift codebases.

Use Cases

  • Case 1: Initializing and configuring a new Swift library with multi-platform support (iOS, macOS, visionOS) using SPM and Package.swift.
  • Case 2: Architecting robust public APIs using protocol-oriented design, value types, and proper access control to ensure a clean consumer interface.
  • Case 3: Implementing automated documentation workflows with DocC and setting up comprehensive unit testing suites using XCTest.
  • Case 4: Managing complex dependency graphs and multi-target package structures for large-scale Swift projects.
  • Case 5: Preparing and archiving XCFrameworks for binary distribution and cross-platform library sharing.
namelang-swift-library-dev
descriptionSwift package development with SPM. Use for Package.swift, public APIs, DocC, XCTest, XCFrameworks, and library publishing. Extends meta-library-dev.

Swift Library Development

Swift-specific patterns for library and package development. This skill extends meta-library-dev with Swift Package Manager tooling, platform support, and ecosystem practices.

This Skill Extends

  • meta-library-dev - Foundational library patterns (API design, versioning, testing strategies)

This Skill Adds

  • Swift tooling: Package.swift, SPM workflows, DocC documentation, XCTest
  • Swift idioms: Protocol-oriented design, value types, availability annotations
  • Swift ecosystem: Platform support, XCFrameworks, dependency management

Quick Reference

Task Command/Pattern
New library package swift package init --type library
Build swift build
Test swift test
Build docs swift package generate-documentation
Resolve dependencies swift package resolve
Archive XCFramework xcodebuild -create-xcframework

Package.swift Structure

Basic Library Package

// swift-tools-version: 5.9
import PackageDescription

let package = Package(
    name: "MyLibrary",
    platforms: [
        .iOS(.v16),
        .macOS(.v13),
        .watchOS(.v9),
        .tvOS(.v16),
        .visionOS(.v1)
    ],
    products: [
        .library(name: "MyLibrary", targets: ["MyLibrary"]),
    ],
    dependencies: [
        .package(url: "https://github.com/apple/swift-log.git", from: "1.5.0"),
    ],
    targets: [
        .target(
            name: "MyLibrary",
            dependencies: [.product(name: "Logging", package: "swift-log")]
        ),
        .testTarget(name: "MyLibraryTests", dependencies: ["MyLibrary"]),
    ]
)

Multi-Target Package

// swift-tools-version: 5.9
import PackageDescription

let package = Package(
    name: "MyPackage",
    platforms: [.iOS(.v16), .macOS(.v13)],
    products: [
        .library(name: "MyPackage", targets: ["MyPackage"]),
        .library(name: "MyPackageTestSupport", targets: ["MyPackageTestSupport"]),
        .executable(name: "my-tool", targets: ["MyPackageCLI"]),
    ],
    dependencies: [
        .package(url: "https://github.com/apple/swift-argument-parser.git", from: "1.3.0"),
    ],
    targets: [
        .target(name: "MyPackage", resources: [.process("Resources")]),
        .target(name: "MyPackageTestSupport", dependencies: ["MyPackage"]),
        .executableTarget(
            name: "MyPackageCLI",
            dependencies: ["MyPackage", .product(name: "ArgumentParser", package: "swift-argument-parser")]
        ),
        .testTarget(name: "MyPackageTests", dependencies: ["MyPackage", "MyPackageTestSupport"]),
    ]
)

Public API Design (Swift-Specific)

Access Control

public func publicFunction() {}      // Part of API
func internalFunction() {}           // Default, not exposed
private func privateFunction() {}    // Same declaration only
fileprivate func filePrivate() {}    // Same file only
open class OpenBaseClass {}          // Subclassable outside module

Protocol-Oriented API

public protocol DataSource {
    associatedtype Item
    func fetch() async throws -> [Item]
}

public extension DataSource {
    func fetchFirst() async throws -> Item? {
        try await fetch().first
    }
}

Value Type APIs

public struct Configuration {
    public var timeout: TimeInterval
    public var retryCount: Int

    public init(timeout: TimeInterval = 30, retryCount: Int = 3) {
        self.timeout = timeout
        self.retryCount = retryCount
    }

    public func withTimeout(_ timeout: TimeInterval) -> Configuration {
        var copy = self
        copy.timeout = timeout
        return copy
    }
}

Availability Annotations

@available(iOS 16.0, macOS 13.0, *)
public func modernFeature() {}

@available(*, deprecated, renamed: "newFunction")
public func oldFunction() { newFunction() }

#if os(iOS)
@available(iOS 16.0, *)
public func iOSOnlyFeature() {}
#endif

Error Handling

Custom Error Types

public enum MyLibraryError: Error, LocalizedError {
    case invalidInput(String)
    case networkError(underlying: Error)
    case notFound(id: String)

    public var errorDescription: String? {
        switch self {
        case .invalidInput(let reason): return "Invalid input: \(reason)"
        case .networkError(let error): return "Network error: \(error.localizedDescription)"
        case .notFound(let id): return "Resource not found: \(id)"
        }
    }
}

Throwing Functions

public func process(_ input: String) throws -> Result {
    guard !input.isEmpty else {
        throw MyLibraryError.invalidInput("Input cannot be empty")
    }
    return Result(value: input)
}

Result Type for Callbacks

public func fetch(completion: @escaping (Swift.Result<Data, MyLibraryError>) -> Void) {
    // For legacy callback APIs
}

Serialization (Codable)

Basic Codable Conformance

public struct User: Codable, Sendable {
    public let id: String
    public let name: String
    public let createdAt: Date

    public init(id: String, name: String, createdAt: Date = .now) {
        self.id = id
        self.name = name
        self.createdAt = createdAt
    }
}

Custom Coding Keys

public struct APIResponse: Codable {
    public let userId: String
    public let userName: String

    private enum CodingKeys: String, CodingKey {
        case userId = "user_id"
        case userName = "user_name"
    }
}

Encoder/Decoder Configuration

public extension JSONDecoder {
    static var myLibrary: JSONDecoder {
        let decoder = JSONDecoder()
        decoder.keyDecodingStrategy = .convertFromSnakeCase
        decoder.dateDecodingStrategy = .iso8601
        return decoder
    }
}

public extension JSONEncoder {
    static var myLibrary: JSONEncoder {
        let encoder = JSONEncoder()
        encoder.keyEncodingStrategy = .convertToSnakeCase
        encoder.dateEncodingStrategy = .iso8601
        encoder.outputFormatting = [.prettyPrinted, .sortedKeys]
        return encoder
    }
}

Zero/Default Values

Default Parameter Values

public struct NetworkClient {
    public let baseURL: URL
    public let timeout: TimeInterval
    public let retryPolicy: RetryPolicy

    public init(
        baseURL: URL,
        timeout: TimeInterval = 30.0,
        retryPolicy: RetryPolicy = .default
    ) {
        self.baseURL = baseURL
        self.timeout = timeout
        self.retryPolicy = retryPolicy
    }
}

public struct RetryPolicy {
    public let maxRetries: Int
    public let delay: TimeInterval

    public static let `default` = RetryPolicy(maxRetries: 3, delay: 1.0)
    public static let aggressive = RetryPolicy(maxRetries: 5, delay: 0.5)
    public static let none = RetryPolicy(maxRetries: 0, delay: 0)
}

ExpressibleBy Protocols

public struct Timeout: ExpressibleByIntegerLiteral, ExpressibleByFloatLiteral {
    public let seconds: TimeInterval

    public init(integerLiteral value: Int) {
        self.seconds = TimeInterval(value)
    }

    public init(floatLiteral value: Double) {
        self.seconds = value
    }
}

// Usage: let timeout: Timeout = 30

Metaprogramming

Property Wrappers

@propertyWrapper
public struct Clamped<Value: Comparable> {
    private var value: Value
    private let range: ClosedRange<Value>

    public var wrappedValue: Value {
        get { value }
        set { value = min(max(newValue, range.lowerBound), range.upperBound) }
    }

    public init(wrappedValue: Value, _ range: ClosedRange<Value>) {
        self.range = range
        self.value = min(max(wrappedValue, range.lowerBound), range.upperBound)
    }
}

// Usage
public struct Volume {
    @Clamped(0...100) public var level: Int = 50
}

Result Builders

@resultBuilder
public struct ArrayBuilder<Element> {
    public static func buildBlock(_ components: Element...) -> [Element] {
        components
    }

    public static func buildOptional(_ component: [Element]?) -> [Element] {
        component ?? []
    }

    public static func buildEither(first: [Element]) -> [Element] { first }
    public static func buildEither(second: [Element]) -> [Element] { second }
}

Swift Macros (5.9+)

// Using macros from dependencies
import Observation

@Observable
public final class ViewModel {
    public var title: String = ""
    public var items: [Item] = []
}

Testing Patterns

XCTest Basics

import XCTest
@testable import MyPackage

final class MyPackageTests: XCTestCase {
    var service: MyService!

    override func setUp() async throws {
        service = MyService()
    }

    func testBasicFunctionality() throws {
        let result = try service.process("test")
        XCTAssertEqual(result.value, "test")
    }

    func testAsyncFunction() async throws {
        let result = try await service.fetchData()
        XCTAssertNotNil(result)
    }

    func testErrorHandling() async {
        do {
            _ = try await service.fetchWithInvalidInput("")
            XCTFail("Should throw error")
        } catch MyLibraryError.invalidInput {
            // Expected
        } catch {
            XCTFail("Wrong error type: \(error)")
        }
    }
}

Test Support Package

// Sources/MyPackageTestSupport/Mocks.swift
public final class MockDataSource: DataSource {
    public var mockItems: [Item] = []
    public var shouldThrowError = false

    public init() {}

    public func fetch() async throws -> [Item] {
        if shouldThrowError { throw MockError.forced }
        return mockItems
    }
}

Module Organization

MyPackage/
├── Package.swift
├── README.md
├── Sources/
│   ├── MyPackage/
│   │   ├── MyPackage.swift      # Main public API
│   │   ├── Models/
│   │   ├── Services/
│   │   └── Internal/            # Private implementation
│   └── MyPackageTestSupport/
└── Tests/
    └── MyPackageTests/

Platform Support

#if os(iOS) || os(tvOS) || os(watchOS)
import UIKit
public typealias PlatformColor = UIColor
#elseif os(macOS)
import AppKit
public typealias PlatformColor = NSColor
#endif

#if canImport(SwiftUI)
import SwiftUI

@available(iOS 16.0, macOS 13.0, *)
public extension Configuration {
    func toView() -> some View {
        ConfigurationView(config: self)
    }
}
#endif

Dependency Management

dependencies: [
    .package(url: "https://github.com/user/repo.git", from: "1.0.0"),  // Semantic version
    .package(url: "https://github.com/user/repo.git", exact: "1.0.0"), // Exact
    .package(url: "https://github.com/user/repo.git", branch: "main"), // Branch
    .package(path: "../LocalPackage"),                                  // Local dev
]

Anti-Patterns

Anti-Pattern Problem Solution
Exposing [String: Any] Loses type safety Return domain types
Classes for value types Unexpected sharing Use structs
Force unwrapping in API Crashes consumer apps Use throws or optionals
Missing @available Breaks older platforms Annotate new APIs

Publishing Checklist

  • All tests pass (swift test)
  • Documentation builds (swift package generate-documentation)
  • No compiler warnings
  • README.md is comprehensive
  • LICENSE file included
  • Version tagged in git
  • CHANGELOG.md updated

References

In This Skill

  • meta-library-dev - Foundational library patterns
  • lang-swift-dev - Swift language fundamentals

External