Things you probably need to know about Swift Package Manager in Xcode - Tanin's blog

Tanin's blog

App Development | Productivity

Things you probably need to know about Swift Package Manager in Xcode

Posted at — Oct 6, 2019

Some abstract image

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.

Table of Contents

  1. Creating a library with SPM in Xcode
  2. Semantic Versioning
  3. Importing packages with SPM in Xcode
  4. Co-existence with CocoaPods (and Carthage)
  5. Migrating to Swift Package Manager
  6. Where to find Swift Packages

Creating a library with SPM in Xcode


As I mentioned, Xcode 11 comes with a New Package option. And that’s where we’re going to start.

1. Create a new package

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

  1. dependencies : Here you specify URLs of other packages this package depends on.

  2. 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.

2. Add dependency packages

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
    ]
)

3. Write code!

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)
        }
        
    }
    
}

Semantic Versioning


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.

Importing packages with SPM in Xcode


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,

  1. Go to File -> Swift Packages -> Add Package Dependency….
  2. On Choose Package Repository screen, enter a source URL. This source URL is identical to the source URLs we include in Package.swift. (in my case, https://github.com/landtanin/USDCurrencyConverter.git) And click next
  3. On Choose Package Options screen, choose the version of the package you need.
  4. Lastly, choose which target is this package for and click finish.

Notice 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

Swift Package Manager in Project Navigator pane

Alamofire appears alongside USDCurrencyConverter, which means it can be used in this project either.

Update to Latest Swift Package Versions

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.

Co-existence with CocoaPods (and Carthage)


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.

CocoaPods and Swift Pacage Dependencies in one project

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.

Migrating from CocoaPods to Swift Package Manager


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.

  1. Go to your project root folder
  2. Download latest script version

    curl https://raw.githubusercontent.com/StatusQuo/spmready/master/main.swift -o spmready.swift
  3. Make it executable

    chmod +x spmready.swift
  4. Run

    ./spmready.swift

And it turns out SwiftJSON is spm-ready!

SwiftJSON is spm-ready

Where to find Swift Packages


We can easily search for CocoaPods libraries on cocoapods.org. What about Swift Packages?

GitHub Package Registry

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.

Swiftpm.co by Dave Verwer

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 🎉

Refs

comments powered by Disqus