Creating and distributing a simple iOS library using Swift Package Manager

The Swift Package Manager (SPM) is a tool for managing the distribution of Swift code. It’s integrated with the Swift build system to automate the process of downloading, compiling, and linking dependencies. With SPM you can easily create a Swift Package, distribute it and also integrate swift packages into your projects.

Swift packages are reusable components of Swift, Objective-C, Objective-C++, C, or C++ code that developers can use in their projects. They bundle source files, binaries, and resources in a way that’s easy to use in your app’s project. They are more or less libraries that you use in your various iOS projects.

This article describes step by step, how you can create and distribute your own Swift Package easily and make it available to developers out there to use using the Swift Package Manager.

In this article, we will be creating a library called Loggr, a simple logging solution for your iOS projects. Let’s get started.


With your Xcode (11.0 and above) opened, select File > New > Swift Packages…

Picture in Picture 2020 09 02 21 57 02 2

We give the package the name, Loggr and save it in a directory. You can tick the option “Create Git repository on my Mac” since we will be distributing it using Git later.

Once completed, Xcode opens with the file Package.swift opened.

Package.swift — Edited 2020 09 02 22 14 06 2

Configurations

The Package.swift file serves as the project manifest file, where configurations can be declared including other dependencies that your Swift Package depend on. In it, we can declare the minimum iOS version on which our library and projects depending on it should run. In this case, we choose iOS 12 as our minimum. Simply add platforms: [.iOS(.v12)] below the name field, as seen below.

Package.swift

You can also specify the Swift version to use, we keep that to default for this library.

If your library depends on other libraries, all you need to do is add the dependency to the dependencies array and specify it under the targets section. For example, say Loggr depends on Alamofire, below is how your dependency would be defined:

Package.swift with dependencies

Since our Loggr library doesn’t depend on any library, we are going to leave the dependencies section empty instead.

Source Code

In the Sources folder is where your source code would be. By default, Xcode has created a file called Loggr.swift which will come in handy for us, as we will add all the needed code in there.

import Foundation
import os.log

public final class Loggr {
            
    public static func logDebug(_ msg: String) {
        #if DEBUG
        os_log(.debug, "[DEBUG]: %s", msg)
        #endif
    }
    
    public static func logInfo(_ msg: String) {
        #if DEBUG
        os_log(.debug, "[INFO]: %s", msg)
        #endif
    }
    
    public static func logWarning(_ msg: String) {
        #if DEBUG
        os_log(.fault, "[WARNING]: %s", msg)
        #endif
    }
    
    public static func logError(_ msg: String) {
        #if DEBUG
        os_log(.error, "[ERROR]: %s", msg)
        #endif
    }

}

Here, we have declared a couple of static functions that could be called to log a message, depending on the kind of desired log level. Notice the class has been declared final to prevent other classes from extending it. Also, both the class and the functions have been declared public to override the default internal access type, so that these class and functions could be visible where needed.

Test classes

The swift package also comes with a generated Tests folder where you can add your desired tests. For the case of this library, we are going to add a few trivial tests to the LoggrTests.swift file.

import XCTest
@testable import Loggr

final class LoggrTests: XCTestCase {
    func testLogDebugSuccess() {
        let exp: XCTestExpectation = expectation(description: "Log Debug get called with no error")
        
        Loggr.logDebug("Debug Message")
        exp.fulfill()

        wait(for: [exp], timeout: 1.0)
    }
    
    func testLogInfoSuccess() {
        let exp: XCTestExpectation = expectation(description: "Log Info get called with no error")
        
        Loggr.logInfo("Info Message")
        exp.fulfill()

        wait(for: [exp], timeout: 1.0)
    }
    
    func testLogWarningSuccess() {
        let exp: XCTestExpectation = expectation(description: "Log Warning get called with no error")
        
        Loggr.logWarning("Warning Message")
        exp.fulfill()

        wait(for: [exp], timeout: 1.0)
    }
    
    func testLogErrorSuccess() {
        let exp: XCTestExpectation = expectation(description: "Log Error get called with no error")
        
        Loggr.logError("Error Message")
        exp.fulfill()

        wait(for: [exp], timeout: 1.0)
    }

    static var allTests = [
        ("testLogDebugSuccess", testLogDebugSuccess),
        ("testLogInfoSuccess", testLogInfoSuccess),
        ("testLogWarningSuccess", testLogWarningSuccess),
        ("testLogErrorSuccess", testLogErrorSuccess),
    ]
}

We run the tests and ensure they all pass. Next is to publish the library.

Publishing to Github

It’s time to publish the package. We do this by basically pushing to Github. We are going to do this by making use of the Version Control System provided by Xcode.

1. Create Remote Repository

If you didn’t tick the “Create Git repository on my Mac” option while creating the project, simply go to Source Control > Create Git Repositories…, then select Loggr as the project and click on the “Create” button.

Now that we have a local git repository created for the project, simply go to the Source Control Navigator, right-click the project name (Loggr) and select Create “Loggr” remote… A dialog pops up where you can fill some necessary details. Select your Github account from the Account section. In case you don’t have your Github account set up already, simply select Add an account… and go through each step required to login to your Github account. Once an account is selected, fill in the description for your library, as the Repository Name is prepopulated with your project name. Then specify the visibility of your project under the Visibility section. Your repository can either be made public or private. Once all that is done, click on the Create button and wait while your repository is being created. Once that is done, the popup is automatically dismissed.

Create Loggr remote
2. Commit and Tag

The next thing to do is to commit our code. Once that is done, it’s time to create a Tag for the project which denotes specific release. This enables users to specify which version of the package they wish to use and SPM will fetch the right version for them.

Simply right-click the project name on the Source Control Navigator, select Tag “master”…, where “master” is the branch name. Specify the tag 1.0.0 to denote initial release of the package. The tag is specified using Semantic Versioning. You can also specify a message that goes with your release. Then click on the Create button.

Monosnap 2020 09 03 22 08 12 1
3. Push

Finally, we simply push using the Xcode Source Control Push… option. Make sure the “Include tags” checkbox is checked.

Monosnap 2020 09 03 22 12 56 1

And voila! We’re done! You can then find the newly created package repository on Github. Now we can go ahead and share the library with everyone and have them use it using Swift Package Manager. ?


To see how this library dependency (Loggr) is added to an Xcode project using the Swift Package Manager, check out this other article of mine, where I wrote a step by step process to add dependencies to your Xcode project using SPM.

Thank you for reading and going through this short journey with me to create a library using SPM. Cheers!