Software Engineer at Capital One UK
Swift Package Manager(SPM) has been around for a while. Since it wasn’t supported in Xcode, it didn’t work with iOS apps. It was only popular amongst Swift server developers. But a lot of things have changed since WWDC19 including the support of SPM in Xcode. Xcode 11 now supports SPM. It even has a New Package option along with New playground, project, workspace options. In this post, I’ll create a simple library, USDCurrencyConverter, to demonstrate the full life-cycle of a library package from creating it, pushing it to GitHub, versioning it, and downloading it to use in an Xcode project.
I was tempted to write about what SPM is in detail, but the official document is so straight forward to read. I highly recommend checking it out if you’re still not sure what it is.
As I mentioned, Xcode 11 comes with a New Package option. And that’s where we’re going to start.
Go to New -> Swift Package… and just follow the instructions. You’ll be prompted with Package.swift
which resides at the root of the project, the manifest file of your library package. It has two main parts
dependencies : Here you specify URLs of other packages this package depends on.
targets : This is where we link the dependencies to targets of this library.
The dependency name in the dependencies block of each target is the name of the library package in its Package.swift
. More on this in the example below.
Here is example Swift.package file for a sample library, USDCurrencyConverter, a simple library for converting from US Dollar to other currencies.
import PackageDescription
let package = Package(
name: "USDCurrencyConverter",
platforms: [
.iOS(.v10),
],
products: [
.library(
name: "USDCurrencyConverter",
targets: ["USDCurrencyConverter"]),
],
dependencies: [
.package(url: "https://github.com/Alamofire/Alamofire.git", from: "5.0.0-rc.2")
],
targets: [
.target(
name: "USDCurrencyConverter",
dependencies: ["Alamofire"]),
.testTarget(
name: "USDCurrencyConverterTests",
dependencies: ["USDCurrencyConverter"]),
]
)
This library uses the famous Alamofire for making an HTTP request. Alamofire is installed by simply adding a source URL to its GitHub page along with the version you want to use in the dependencies section. Note that the version number here is a String.
In the targets section, with the Alamofire dependency ready, we specify what dependencies we want in our targets. Alamofire package name is “Alamofire”, and that’s what we use to link the USDCurrencyConverter target to Alamofire dependency.
And just like in the test classes of the test target in most Xcode projects where we need to @testable import OurProject
, Xcode auto generates the test target for this project as well as includes a dependency to the package by its name.
Here is a sneak peek into Alamofire’s Package.swift.
import PackageDescription
let package = Package(
name: "Alamofire",
platforms: [
.macOS(.v10_12)
.iOS(.v10),
.tvOS(.v10),
.watchOS(.v3)
],
products: [
.library(
name: "Alamofire",
targets: ["Alamofire"])
],
targets: [
.target(
name: "Alamofire",
path: "Source")
],
swiftLanguageVersions: [
.v5
]
)
Your source code for the library will be under Sources -> YourProjectName folder. This library will simply provide one class which must be initialised with an apiKey (Currency exchange rates will be retrieved from API from https://currencylayer.com). This class will provide a method convertUSDTo(currency:)
, which will call the currency API using Alamofire and return the currency exchange data via a completion closure.
import Alamofire
import Foundation
public class USDCurrencyConverter {
private var apiKey: String
public init(apiKey: String) {
self.apiKey = apiKey
}
public func convertUSDTo(currency: Currency, completion: @escaping (Data?) -> Void) {
let url = URL(string: "http://apilayer.net/api/live?")!
AF.request(
url, method: .get,
parameters: [
"access_key":apiKey,
"currencies":currency.rawValue,
"format":"1"
]
).response { (response) in
completion(response.data)
}
}
}
USDCurrencyConverter is almost ready for use. The development work is done. What is left is to Git tag the commits with version numbers. Swift packages follow Semantic Versioning (semver.org) conventions. Here is a version number format of this convention: MAJOR.MINOR.PATCH.
Pre-releases can be specified by adding identifiers after the version number. Examples: 1.0.0-alpha
, 1.0.0-alpha.1
. Here are the four main stages of version identifiers ordered by precedence alpha
, beta
, rc
(Release Candidate), and the release version. In the example above, Alamofire version number in the dependencies block was 5.0.0-rc.2
.
GitHub provides a convenient tool for Git tagging your commits, the release page of your GitHub repo. You can easily draft a new release, give a version number which follows the convention, and give a little detail about the release. Here is USDCurrencyConverter release page.
USDCurrencyConverter library is going to be used in my sample project from SwiftUI at iOSDevUK 9 post. The project has a class called ExchangeRate
which I hardcoded the exchange rate for USD to GBP. I’ll use USDCurrencyConverter to properly retrieve this volatile exchange rate.
There are a few ways of doing this. The official document shows us one way of doing it. So I’ll walk you through the other. With an app project opened in Xcode,
Package.swift
. (in my case, https://github.com/landtanin/USDCurrencyConverter.git) And click nextNotice that it is very similar to when we imported Alamofire in USDCurrencyConverter Package.swift
. We added a source URL and specified its version. Then we linked dependencies to targets.
Also, notice the Swift Package Dependencies in the Project Navigator pane
Alamofire appears alongside USDCurrencyConverter, which means it can be used in this project either.
I just want to quickly mention this tool Xcode provides us. During the development of USDCurrencyConverter, I had to update the library to make it function properly in the app for a few times. This tool does exactly what it says on the tin. In the picture above, notice the version number at the end of each package. As soon as I tagged a new release version number on the library GitHub page, the only step needed was File -> Swift Pacakges -> Update to Latest Swift Package Versions. Xcode pulled the new version and updated the version number shown on the Project Navigator pane automatically.
Swift Package Manager can work alongside CocoaPods and Carthage… Let’s try it out!
USDCurrencyConverter.convertUSDTo(currency:)
will provide a Data
response object from the API. To demonstrate the ability to co-exist with CocoaPods, I’ll install SwiftyJSON via CocoaPods for JSON parsing.
There’s not much to say here. It works. Here is the ExchangeRate
class in case you’re interested in how my USDCurrencyConverter
library works with SwiftyJSON
.
import SwiftUI
import SwiftyJSON
import USDCurrencyConverter
class ExchangeRate: ObservableObject {
@Published var usdToGbp : Double = 0
#error("GET YOUR API KEY AT https://currencylayer.com/")
let apiKey = "YOUR_API_KEY"
init() {
let converter = USDCurrencyConverter(apiKey: apiKey)
converter.convertUSDTo(currency: .GBP) { [weak self] data in
guard let resultData = data, let json = try? JSON(data: resultData) else {
debugPrint("json empty")
return
}
if let usdToGpbDouble = Double(json["quotes"]["USDGBP"].stringValue) {
self?.usdToGbp = usdToGpbDouble
}
}
}
}
I put #error
there to make sure the app won’t compile without a unique API key. Get the code of this app at here
If anyone is coding along here, you will also need to set App Transport Security Settings
-> Allow Arbitrary Loads
to YES
since the api request uses HTTP protocol.
Swift Package Manager is still in its early days. However, it feels so right to use a package manager which supports by Xcode out of the box. And it’s great to see some effort from the community to help us adopting Swift Package Manager. Humi wrote a script to help us find out whether our pods are spmready. You can find out about his project in this article. For now, let’s see this script in action.
curl https://raw.githubusercontent.com/StatusQuo/spmready/master/main.swift -o spmready.swift
chmod +x spmready.swift
./spmready.swift
And it turns out SwiftJSON is spm-ready!
We can easily search for CocoaPods libraries on cocoapods.org. What about Swift Packages?
In May 2019, GitHub announced GitHub Package Registry, a package management service. It supports many familiar package management tools such as JavaScript (npm), Java (Maven), and Docker images. A month later, they announced support for Swift packages in which GitHub and Apple are working on together. You can use GitHub Package Registry to manage and find public packages via the same familiar GitHub interface. However, it’s still in beta. You can’t seem to use it to search for packages yet. But it’s still worth signing up to receive email updates about the tool.
I heard of Swiftpm.co from Dave Verwer himself during his talk at iOSDevUK 9. He acknowledged the existence of GitHub Package Registry. But as I mention, it’s still in beta. Until then, this another great community tool is my go-to option to find Swift packages. Again, check this out at Swiftpm.co, and if there are spm-ready libraries which are not on the site yet, you can add them to the list here.
As always, here are links to the sample projects
That’s it for this post. Hope you found something useful. Please feel free to comment below if you have any suggestions or questions. Thanks so much for reading and happy coding 🎉