Skip to Content
Course content

49: Swift Package Manager for Libraries

Click on the "Edit" button in the top corner of the screen to edit your slide content.

Think of creating a Swift library like building a specialized tool kit. If you're a carpenter, you don't build a new hammer and a new saw from scratch every single time you start a new project. Instead, you have a dedicated toolkit that you carry from job to job. You don't necessarily care how the hammer was forged; you just care that it works and that it's available whenever you open that box.

In Swift, a Package is that toolkit. Instead of copying and pasting a handful of helper classes from one app project to another, you wrap that logic into a Swift Package. The Package.swift file is essentially the "manifest" or the inventory list of your toolkit—it tells Swift what's inside, what other toolkits are needed to make it work, and which tools are actually available for the end user to grab.

Defining Your Toolkit in Package.swift

When you create a new library package, the heart of everything is the Package.swift file. I've seen a lot of developers get intimidated by this file because it looks like a configuration script, but it's actually just Swift code. You're defining a Package object.

Let's say we're building a library called ColorPalette that helps developers generate complementary colors for their UI. Your manifest would look something like this:

import PackageDescription

let package = Package(
    name: "ColorPalette",
    products: [
        // This is what the 'customer' sees. 
        // We're exporting the library so other projects can import it.
        .library(name: "ColorPalette", targets: ["ColorPalette"]),
    ],
    dependencies: [
        // If our color library needed another library (like a math utility), 
        // we'd list it here. For now, we're keeping it lean.
    ],
    targets: [
        // This is where the actual code lives.
        .target(name: "ColorPalette", dependencies: []),
        .testTarget(name: "ColorPaletteTests", dependencies: ["ColorPalette"]),
    ]
)

Notice the distinction between products and targets. A target is a collection of files that get compiled together. A product is how you "package" those targets for others to use. You might have five different targets for internal organization, but you only expose one single library product to the public.

The "Public" Hurdle

Here is the most common mistake I see when people move from app development to library development: forgetting the public keyword.

In a standard app project, most of your classes and functions are internal by default. That works fine because everything is in one big bucket. But libraries are different. If you write a great function in your library but don't explicitly mark it as public, the person importing your library won't be able to see it. It's like putting a tool in your kit but locking it inside a safe that only you have the key to.

If our ColorPalette library looks like this, it's useless to anyone else:

struct PaletteGenerator {
    func generateComplementary(to color: String) -> String {
        return "Complementary of \(color)"
    }
}

To make it actually work as a library, you have to be explicit:

public struct PaletteGenerator {
    public init() {} // Don't forget the initializer! 
    
    public func generateComplementary(to color: String) -> String {
        return "Complementary of \(color)"
    }
}

I mentioned the init because that trips people up constantly. Even if your struct is public, the default memberwise initializer is internal. If you want someone to be able to write let gen = PaletteGenerator() in their own app, you have to write that public init() {} yourself.

Organizing the Folder Structure

SPM is very opinionated about where files go. If you stray from the expected folder structure, the compiler will just stare at you blankly. For a library, your folder hierarchy should look like this:

  • Root Folder
    • Package.swift
    • Sources
      • ColorPalette (This folder name MUST match the target name)
        • PaletteGenerator.swift
    • Tests
      • ColorPaletteTests
        • PaletteGeneratorTests.swift

If you name your target ColorPalette in the manifest but name the folder ColorPaletteLogic, SPM won't find your code. It's a strict mapping, so keep them identical.




📋 Practical Task

Build a "StringMetric" Utility Library

Your goal is to create a Swift Package that provides a utility for analyzing strings. This will test your ability to set up a manifest and handle access modifiers correctly.

Requirements:

  • Create a package named StringMetric.
  • In the Package.swift file, define one library product and one target, both named StringMetric.
  • Inside the Sources/StringMetric folder, create a public struct called TextAnalyzer.
  • Implement a public function inside TextAnalyzer called wordCount(in text: String) -> Int that returns the number of words in a given string.
  • Ensure TextAnalyzer has a public init() so it can be instantiated from outside the package.
  • Verify that the folder structure matches the target names exactly.
Rating
0 0

There are no comments for now.

to be the first to leave a comment.