Encapsulating code so that you can share it between your own projects and with other programmers has always been a bit tricky in Xcode. You can write a framework easily enough, but sharing frameworks between apps is not simple, and it’s even harder to distribute your code to others as a framework, and for others to embed your framework in their own app. And things are made more complicated by the need to manage dependencies.
For a long time, the predominant mechanism for distributing libraries, faute de mieux, has been Cocoapods. But this is not a native mechanism; it’s a Ruby tool (based on Ruby Bundler). It modifies your project extensively, and can be fragile and difficult to configure.
Starting in Xcode 11, Apple has made available a far more convenient mechanism: Swift packages. The primary purpose of a Swift package is to share your code as open source. A package is simpler and more efficient than a framework, because it is basically just a collection of source code, which doesn’t need linking. Swift packages have the potential to supersede Cocoapods with a built-in native official dependency management mechanism, and in my view, it will be a good thing if they can do so.
Let’s Make a Package!
Making a package is pretty simple. The best approach is to create the package and add it to a project, so that you can use the project as a test bed for running the package code. Let’s try it:
In an existing project (which can be an empty "vanilla" project you’ve just created from the app template), choose File > New > Swift Package.
Give the package a name, such as MyCoolPackage. At the bottom of the Save dialog, specify that you want to add this package to the existing project, and make sure you’re adding it at the top level of the project, not inside any group. Observe that this is a folder and will not necessarily be placed inside your project folder, though you can specify that if you want to. Click Create.
The initial package files appear in the Project navigator, but this module is not yet available to the app target. Edit the target; in the General pane, under Frameworks, Libraries, and Embedded Content, click Plus and choose the package library in the dialog. Click Add.
In your app target’s code, you can now
import MyCoolPackage to access public types declared in the package. There is already one source file, MyCoolPackage.swift, ready for you to play with.
A package is not a framework; in fact, it’s not even a full-fledged target. There are no build settings or build phases, as there would be for a real target. Instead, at the top level of the package there’s a configuration file, Package.swift, consisting primarily of a single call to the Package class initializer. This call is the package manifest.
In effect, the package manifest does in code for a package what you would have done with build settings and phases for a full-fledged target. The code declares a "target" called
"MyCoolPackage"; this name corresponds to the MyCoolPackage group inside Sources, so that whatever is inside that group, such as the MyCoolPackage.swift file, goes into that target. If you add code files to the package, add them inside this same group, so that they too are compiled as part of the "target".
Here’s the really interesting part. When your app uses a Swift package consisting of code files, then when you build the app, nothing new is visibly added to the built app. There is no Frameworks folder, and there is no additional embedded bundle, as there would be if this were a framework. So where did the package code go? It has been compiled together with your app target’s code, and is part of your app’s binary. There is no need for linking! In effect, the package has been incorporated directly into the app target.
When packages were introduced in Xcode 11, I took a look at them and immediately discovered a severe limitation: they had to consist only of code. You couldn’t include images, a storyboard, or anything else that wasn’t code. So I decided packages weren’t ready for prime time, and I wasn’t prepared to use them yet. In Xcode 12, all of that has changed. Now you can add bundle resources, as well as localizations, to your Swift package.
To illustrate: Suppose we want to add an asset catalog. Select the existing source file, MyCoolPackage.swift, and choose File > New > File. In the template chooser, choose iOS > Resource > Asset Catalog. We have now added the asset catalog to the "target."
The result is that now the built app will contain an actual bundle, called _MyCoolPackageMyCoolPackage.bundle, created at top level, containing this asset catalog. To access an image in the asset catalog, code in the package must specify the bundle; the easiest way to do that is with the convenience variable
Bundle.module, which is implemented for you by the build process. (Your app’s own code can also specify the bundle, but not so easily; the expectation is that resources in a package are for direct use by the package, not the surrounding app.)
You can also include individual files or folders as resources in your package, but in that case the package build mechanism needs to be told what to do with them. You do that by editing the
.target value in the package manifest:
A file that you don’t want to add to the target would be added to the target’s
A file that you do want to add to the target would be added to the target’s
resources:array as a
.processResource object; for an entire folder, it would be a
(For more details, there’s a good WWDC 2020 video on this topic.)
Using a Package
What we’ve created is a local package. But one of the key features of packages is that they can easily be made public. You place your package under git management; then you can upload (push) your package to an online remote git repository, such as GitHub. That’s easy for you to do. Then, other programmers can incorporate it into their projects. That’s easy for them to do.
To demonstrate, let’s turn the tables and see how easy it is for you to incorporate other programmers’ public packages into your projects. For example, to add to your project the new Swift Numerics package:
Choose File > Swift Packages > Add Package Dependency.
Enter the URL https://github.com/apple/swift-numerics into the field in the dialog.
Click Next (twice).
Check the checkbox next to Numerics in the final dialog, and click Finish.
The package is downloaded and made available to your project, and now you can
import Numerics in your project’s code files and use the package code.
Interestingly, the package code is shown in your project, but the package is not stored in your project folder. It’s stored in Xcode’s DerivedData folder, where Xcode can incorporate it into your project’s build process without polluting your project. From now on, whenever you open your project, Xcode will check to see whether it has the package code, and if it doesn’t, it will download it there and then.
When Xcode checks to see whether you’ve got the package code, it also checks the version of the package code. This is one of the most important features of Swift packages. Xcode can ascertain online whether you’ve got the latest official version of a package, and will update the package source if you don’t. Choose File > Swift Packages > Update to Latest Package Versions to make Xcode go online and check for updates. Moreover, a package can declare a dependency, meaning that it relies on some other package — and Xcode will also download that package and make sure that it stays up to date. The package version numbering system and the rules for determining whether a dependency needs updating are quite sophisticated (I’m not going to go into details here).
The trick is that when you upload your own package, or when you update your package, you need to declare its version number. You do this by attaching a git tag, in the form of a version string, to the most recent commit. For example:
% git tag "0.0.1" % git push --tags
Xcode’s package management mechanism works fine with private repositories, so you can share your code without making it public; you can upload your package and share it with yourself or with your team. This is a great way to factor out common code and use it in different projects.
That’s enough to get you started with Swift packages. I have not gone into the nitty-gritty of the all-important package manifest; to learn more about that, consult the relevant WWDC videos, plus there’s a nice explanation in the online documentation. Swift packages are now mature and ready for use, and I think it’s time for me to start using them. When you experiment with them a little, I bet you’ll feel the same way.