Mastering Modularization: Building Swift Packages with Swift Package Manager

Swift InsightsSwift Insights
7 min read

Introduction

Hi folks, today we're diving into the importance of modularization in Swift application development. Later in this post, I'll walk you through creating your own package using Swift Package Manager and how to integrate it into other Xcode projects.

Why Modularization Matters

Before we get started, let's explore the benefits of modularization. In today's fast-paced software development world, keeping a clean and efficient codebase is crucial. Modularization, a technique that divides your software into separate, manageable modules, is key to achieving this. Here are some of its benefits:

  • Improved Code Organization: makes your code easier to manage and navigate.

  • Reusability: allows you to reuse code across different projects, saving time and effort.

  • Scalability: facilitates scaling projects by enabling independent development of modules.

  • Testing: enables isolated testing of modules.

  • Collaboration: allows multiple developers to work on different modules simultaneously.

Understanding these benefits highlights the value of modularization. Now, let's get to work.

Creating a Swift Package

A network manager would be an ideal candidate for modularization since most Swift applications interact with the network to fetch and post data. So in this post, I'll show you how to create a reusable package to deal with network requests. However, I won't dive deeply into the implementation details of network manager, as that's not the main focus of this post. In general, I used protocols and generics to support various request and response types and the Swift concurrency framework for handling asynchronous tasks. I also stubbed network requests using URLProtocol to write unit tests for the network manager. If you are interested in the implementation details, I have posted the link to the package at the end of this post.

Let's start creating the package using Xcode. To do this, select File -> New -> Package from the Xcode menu.

Next, let's name the Swift package as NetworkManager. Make sure to check the "Create Git Repository" option βœ… as shown in the screenshot below. Then, click "Create" πŸš€.

Once done you'll notice the structure of the project is a bit different from a typical iOS project structure that you are used to. The main difference is that, instead of a <appname>.xcodeproj file, we have a Package.swift file.

Here is how the Package.swift file will look like:

The Package.swift file is the manifest file used by Swift Package Manager (SPM). This file is essential for SPM to resolve dependencies, build the package, and integrate it into other Swift projects.

Here are the key configurations that can be used with the Package.swift file and their purposes:

  • Name: The name of the package.

  • Platforms: Specifies which platforms (like macOS, iOS, Linux) the package supports and any minimum version requirements.

  • Products: Lists any products (libraries or executables) that should be built from the targets.

  • Dependencies: Lists the dependencies required by the package. These can be other Swift packages or external libraries needed for the package to function.

  • Targets: Specifies the targets (modules of Swift code) that are part of the package.

We can see name, products and targets configurations are already included by default. Other than that, we need to include platform dependencies for our package. Since the network manager will be used in both iOS and macOS projects, I am going to include both iOS and macOS platforms as shown below:

Note that our package does not depend on any third-party packages. So, we won't be adding any dependencies to the manifest file. However, let's say you want to use SnapKit as a dependency for your package, you can add the following section to the dependencies array in the manifest file:

But make sure to include the dependencies before the targets, as the order does matter. The expected order in the manifest file is: name -> platforms -> products -> dependencies -> targets.

Next, we’ll include the implementation files for the network manager. All implementation files should go inside the Sources folder.

After that, there’s an important thing to keep in mind. That is Access control! By default, everything in Swift is internal, meaning that those functions and classes won’t be visible outside this module. Ensure that everything you need to expose outside this package is declared as open or public. For more information about access control, you can refer to Apple's documentation here.

Once everything is in place, make sure the project builds successfully.

Next, let's go ahead and include the unit test files for the package. Unit test files should be created inside the Tests folder.

To run the unit tests, you'll need to switch to the test scheme. If there is no test scheme available, you can create a new one as follows.

Next, select the test scheme and press ⌘ + U. Make sure all tests are passing βœ….

Finally, we need to add a README file for the package. This file will serve as the front page of our package on GitHub and will also be visible to users when importing the package through Swift Package Manager in Xcode. In the README file, you should include details on how to install and how to use the package in an iOS or macOS project πŸ“š.

To create the README file:

  1. Right-click on the package name.

  2. Click on New File πŸ“„.

  3. Rename the File: Name it README.md ✏️.

  4. Include Package Details: Add the necessary information about the package inside the README file πŸ“.

Now the setup is complete. Next, we need to push the package to GitHub. Before that, if you haven’t already configured Xcode to connect with your GitHub account yet, please follow this link and set it up.

Once your GitHub account is configured:

  1. Open Source Control Navigator: Go to Source Control Navigator -> Repositories in Xcode πŸ”.

  2. Right-click on the Package Name

  3. Click on New Remote: Select New "NetworkManager" Remote to add your GitHub repository 🌐.

Then you'll get a prompt to create the GitHub repository. You can change the default package name if needed and set the visibility to Public 🌍. Then, click Create πŸš€.

This will push your package to GitHub, making it available for others to use and collaborate on πŸš€.

Now we need to commit our changes to the GitHub repository we just created. To do this:

  1. Press ⌘ + βŒ₯ + C.

  2. Stage the Changes

  3. Add a Commit Message: Enter a descriptive commit message πŸ“.

  4. Click Commit βœ….

Before pushing your changes to GitHub, we need to version our package. Versioning is important when importing the package into other projects and also it helps us to maintain different versions of the package πŸ“¦.

To create a version:

  1. Open Source Control Navigator: Go to Source Control Navigator -> Repositories in Xcode πŸ”.

  2. Right-click on the Package Name.

  3. Click on Tag "main".

In the popup, enter a version number and a message, then click Create 🎯.

This will tag your current state as a version, making it easier to manage and track different versions of your package.

Now, let’s push our changes to GitHub. Make sure to check βœ… the Include tags option before pushing.

Now, head over to GitHub and verify everything is in order. Inside the newly created NetworkManager repository, you should see:

  • The updated README with the package description πŸ“„.

  • One tag, which is listed under the Releases section 🏷️.

Now it's time to test our newly created packageπŸ€—. Switch back to Xcode and create a new iOS projectπŸ“±.

Let’s give a name to the new project and save it πŸ“. To import our package, we need the URL of the NetworkManager repo. Switch back to GitHub and go to your repository, click the green Code button, and copy the URL 🌐.

Then, switch back to Xcode, go to the Project Navigator, and select Package Dependencies as shown in the screenshot.

Click the βž• button. In the popup, paste the copied URL into the search box πŸ”. You should then see our newly created package.

Go ahead and click Add Package.

Once that is done, open the ViewController.swift file and you should be able to import the NetworkManager package as shown belowπŸ“¦. Thats it! πŸ₯³.

Conclusion

And that’s a wrap! πŸŽ‰ We've successfully created, uploaded, and integrated a Swift package into a new project. This process involved setting up the package, managing its versions, and finally importing it into an Xcode project for use. By now, you should have a solid understanding of how to modularize your code using Swift packages, making your projects cleaner and more manageable.

I hope you find this guide helpful and informative. Here is the link to the GitHub Repo I have already created. If you have any questions or need further assistance, feel free to reach out. Happy coding! 🌟

Until next time... Ciao! πŸ‘‹

0
Subscribe to my newsletter

Read articles from Swift Insights directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Swift Insights
Swift Insights

A seasoned Senior iOS Engineer with more than 11 years of experience developing high-performance mobile applications. Committed to writing clean code, thorough testing, and following Agile practices. Skilled in leading teams and integrating advanced features. Familiar with leveraging cutting-edge Apple technologies to create user-centric and effective applications.