Mastering Modularization: Building Swift Packages with Swift Package Manager
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:
Right-click on the package name.
Click on New File π.
Rename the File: Name it
README.md
βοΈ.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:
Open Source Control Navigator: Go to Source Control Navigator -> Repositories in Xcode π.
Right-click on the Package Name
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:
Press β + β₯ + C.
Stage the Changes
Add a Commit Message: Enter a descriptive commit message π.
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:
Open Source Control Navigator: Go to Source Control Navigator -> Repositories in Xcode π.
Right-click on the Package Name.
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! π
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.