Adding Swift packages in Xcode
Learn how to add the popular Swift networking package Alamofire in a Swift Xcode project
On this page
This project walks through how to add third-party packages to your Swift projects in Xcode
Prerequisites
- Swift (this post uses Swift 6.1).
Initializing a Swift executable package
You can create a new Swift executable package with the following (from the command line):
$ mkdir hello-swift
$ cd hello-swift
$ swift package init --type executable
You can run swift package init --help to get more information around arguments like --type. For example:
swift package init --help
OVERVIEW: Initialize a new package
USAGE: swift package init [--type <type>] [--enable-xctest] [--disable-xctest] [--enable-swift-testing] [--disable-swift-testing] [--name <name>]
OPTIONS:
--type <type> Package type: (default: library)
library - A package with a library.
executable - A package with an executable.
tool - A package with an executable that uses
Swift Argument Parser. Use this template if you
plan to have a rich set of command-line arguments.
build-tool-plugin - A package that vends a build tool plugin.
command-plugin - A package that vends a command plugin.
macro - A package that vends a macro.
empty - An empty package with a Package.swift manifest.
--enable-xctest/--disable-xctest
Enable support for XCTest
--enable-swift-testing/--disable-swift-testing
Enable support for Swift Testing
--name <name> Provide custom package name
--version Show the version.
-h, -help, --help Show help information.
With this new package initialized as an executable, you can navigate to Sources/main.swift to see the generated entry file:
/ The Swift Programming Language
// https://docs.swift.org/swift-book
print("Hello, world!")
Running our executable
You can make use of swift run to run our executable:
$ swift run
[1/1] Planning build
Building for debugging...
[1/1] Write swift-version--58304C5D6DBC2206.txt
Build of product 'hello-swift' complete! (0.19s)
Hello, world!
We can re-use this to run after any file changes:
$ swift run
Building for debugging...
[7/7] Applying hello-swift
Build of product 'hello-swift' complete! (1.01s)
Hello again, world!
If you have a program installed such as watchexec, you can set it to watch swift extension files with watchexec --exts swift -- swift run :
$ watchexec --exts swift -- swift run
[Running: swift run]
[1/1] Planning build
Building for debugging...
[1/1] Write swift-version--58304C5D6DBC2206.txt
Build of product 'hello-swift' complete! (0.20s)
Hello again, world!
[Command was successful]
[Running: swift run]
[1/1] Planning build
Building for debugging...
[7/7] Applying hello-swift
Build of product 'hello-swift' complete! (0.73s)
Hello again and again, world!
[Command was successful]
Building files
You can run swift build from the project root to build out the executable.
$ swift build
[1/1] Planning build
Building for debugging...
[1/1] Write swift-version--58304C5D6DBC2206.txt
Build complete! (0.23s)
By default, swift build will build with debug configuration.
Outputs are placed in .build:
$ tree .build -L 3
.build
├── arm64-apple-macosx
│ └── debug
│ ├── description.json
│ ├── hello_swift.build
│ ├── hello-swift
│ ├── hello-swift-entitlement.plist
│ ├── hello-swift.dSYM
│ ├── hello-swift.product
│ ├── index
│ ├── ModuleCache
│ ├── Modules
│ ├── plugin-tools-description.json
│ └── swift-version--58304C5D6DBC2206.txt
├── artifacts
├── build.db
├── checkouts
├── debug -> arm64-apple-macosx/debug
├── debug.yaml
├── plugin-tools.yaml
├── repositories
└── workspace-state.json
For an optimized build, pass --configuration release:
$ swift build --configuration release
Building for production...
[5/5] Linking hello-swift
Build complete! (1.21s)
$ tree .build -L 3
.build
├── arm64-apple-macosx
│ ├── debug
│ │ ├── description.json
│ │ ├── hello_swift.build
│ │ ├── hello-swift
│ │ ├── hello-swift-entitlement.plist
│ │ ├── hello-swift.dSYM
│ │ ├── hello-swift.product
│ │ ├── index
│ │ ├── ModuleCache
│ │ ├── Modules
│ │ ├── plugin-tools-description.json
│ │ └── swift-version--58304C5D6DBC2206.txt
│ └── release
│ ├── description.json
│ ├── hello_swift.build
│ ├── hello-swift
│ ├── hello-swift.dSYM
│ ├── hello-swift.product
│ ├── ModuleCache
│ ├── Modules
│ ├── plugin-tools-description.json
│ └── swift-version--58304C5D6DBC2206.txt
├── artifacts
├── build.db
├── checkouts
├── debug -> arm64-apple-macosx/debug
├── debug.yaml
├── plugin-tools.yaml
├── release -> arm64-apple-macosx/release
├── release.yaml
├── repositories
└── workspace-state.json
20 directories, 14 files
Once compiled, you'll have an optimized binary you can run directly (if your platform is the correct target):
$ ./.build/release/hello-swift
Hello again and again, world!
Other useful flags include --triple to cross-compile for another platform.
Makefile
You can make use of a Makefile for common operations:
.PHONY: build run clean release test
build:
swift build
release:
swift build -c release
run:
swift run
test:
swift test
clean:
swift package clean
In use:
$ make clean
swift package clean
$ make build
swift build
Building for debugging...
[8/8] Applying hello-swift
Build complete! (1.06s)
$ make run
swift run
[1/1] Planning build
Building for debugging...
[1/1] Write swift-version--58304C5D6DBC2206.txt
Build of product 'hello-swift' complete! (0.21s)
Hello again and again, world!
Managing packages
You can manage packages in Package.swift or via the command line.
The default file that was created:
// Package.swift
// swift-tools-version: 6.1
// The swift-tools-version declares the minimum version of Swift required to build this package.
import PackageDescription
let package = Package(
name: "hello-swift",
targets: [
// Targets are the basic building blocks of a package, defining a module or a test suite.
// Targets can depend on other targets in this package and products from dependencies.
.executableTarget(
name: "hello-swift"),
]
)
From the command line, you can install a package using swift package add-dependency <URL>:
$ swift package add-dependency https://github.com/Alamofire/Alamofire --from 5.10.2
Updating package manifest at Package.swift... done.
In the above case, the package is installed via the GitHub repository for the popular networking package Alamofire.
You must also provide a version.
$ swift package add-dependency https://github.com/Alamofire/Alamofire
error: must specify one of --exact, --branch, --revision, --from, or --up-to-next-minor-from
Once installed, your Package.swift file will be updated:
// swift-tools-version: 6.1
// The swift-tools-version declares the minimum version of Swift required to build this package.
import PackageDescription
let package = Package(
name: "hello-swift",
dependencies: [
.package(url: "https://github.com/Alamofire/Alamofire", from: "5.10.2"),
],
targets: [
// Targets are the basic building blocks of a package, defining a module or a test suite.
// Targets can depend on other targets in this package and products from dependencies.
.executableTarget(
name: "hello-swift"),
]
)
In order to use it, you must update the relevant targets:
// swift-tools-version: 6.1
// The swift-tools-version declares the minimum version of Swift required to build this package.
import PackageDescription
let package = Package(
name: "hello-swift",
dependencies: [
.package(url: "https://github.com/Alamofire/Alamofire", from: "5.10.2"),
],
targets: [
// Targets are the basic building blocks of a package, defining a module or a test suite.
// Targets can depend on other targets in this package and products from dependencies.
.executableTarget(
name: "hello-swift",
dependencies: [
"Alamofire" // 👈 add this line
]),
]
)
We can see this in action by updating our main.swift file:
// Sources/main.swift
// The Swift Programming Language
// https://docs.swift.org/swift-book
import Alamofire
import Foundation
@main
struct Hello {
static func main() {
AF.request("https://jsonplaceholder.typicode.com/todos/1")
.responseString { response in
print(response.value ?? "no data")
CFRunLoopStop(CFRunLoopGetMain()) // stop loop when done
}
// keep the program alive until CFRunLoopStop is called
CFRunLoopRun()
}
}
Let's see it in action:
$ swift run
Fetching https://github.com/Alamofire/Alamofire
Fetched https://github.com/Alamofire/Alamofire from cache (22.64s)
Computing version for https://github.com/Alamofire/Alamofire
Computed https://github.com/Alamofire/Alamofire at 5.10.2 (23.82s)
Creating working copy for https://github.com/Alamofire/Alamofire
Working copy of https://github.com/Alamofire/Alamofire resolved at 5.10.2
[1/1] Planning build
Building for debugging...
[52/52] Applying hello-swift
Build of product 'hello-swift' complete! (6.15s)
{
"userId": 1,
"id": 1,
"title": "delectus aut autem",
"completed": false
}
Perfect, we got our response from an installed package.
Conclusion
This post was recounting some baby steps with initializing Swift packages. I will likely be writing a few tools for MacOS with this approach for learning more about the language while diving into iOS.
Links and Further Reading
Photo credit: johnnyb803