lang-swift-library-dev
Swift package development with SPM. Use for Package.swift, public APIs, DocC, XCTest, XCFrameworks, and library publishing. Extends meta-library-dev.
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.
| name | lang-swift-library-dev |
|---|---|
| description | Swift 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
Related Skills
meta-library-dev- Foundational library patternslang-swift-dev- Swift language fundamentals