During WWDC 2019 Apple announced that Swift Package Manager (SPM) now supports iOS projects. iOS developers had been waiting for a long time for this feature. In the past, only Mac projects have benefited from SPM. iOS developers have learned to use CocoaPods and Carthage but those tools are not perfect and SPM should allow us to replace any need in third-party dependency manager.
Many big companies separate their code into multiple internal frameworks and reuse them across their application. We have the same approach In the company I am working for right now – IG. I have decided to try SPM for managing our dependencies.
There are 2 kinds of dependencies: third-party and internal. SPM supports both. It even supports complex dependency graphs with nesting. It sounded like an ideal option until I started to hit some problems.
Starting is easy
It’s easy to get started, all you need to do is to add
package.swift file to your project root directory. Open this file with Xcode 11 or newer and it will try to parse it. Xcode project file is no longer necessary and instead of projects, you will work with packages. Select File > New Swift Package to create an empty package, you can use generated
package.swift. Make sure you can build and test. You might need to fix some errors. For example, I had to add
import Foundation to some of my source files. You also will need to specify the location for your source if it’s not in the default Sources folder. Other problems were much harder to fix.
Problems and limitations
The first thing I noticed is that not all third-party dependencies support SPM. If yours does, you are a lucky person but if it’s not — there’s nothing you can do, besides letting them know or abandoning the dependency altogether. For example, I have managed to integrate with Alamofire and SwiftLint but not jazzy. If your dependency is closed source binary it won’t work with SPM at all! If you have something like
name.framework in your project you won’t be able to wrap them in your own Swift package.
The second issue is the lack of the Run Script build phase. If you used to run SwiftLint every time you build your project, this won’t work with the Swift package. Alternatively, you could use a git hook based approach but this will require a lot of third-party code, and I couldn’t get it to work in a short time.
I also have noticed that you won’t be able to read local files through
Bundle class. Following
nil in Swift package:
let path = bundle.url(forResource: "filename", withExtension: "json")
That is caused by the lack of
Bundle in the Swift package. If you want to read files locally use
FileManager. You can separate code which only runs in Swift package with
#if SWIFT_PACKAGE condition statement.
And finally, if you depend on any Objective-C code, you won’t be able to build it with your package. I was able to exclude some of my Objective-C code but I am not sure it won’t cause issues later.
Conclusion and benefits
If you can use SPM, you should do it, it’s easy to add a package with the new Xcode GUI tool. If you providing a library, you should add SPM support for it. When it works it is great, it allows to set breakpoints and see errors in third-party packages, displays all third-party code (you will be surprised how many nested third-party projects your app depends on). It validates your dependency settings and allows you to remove the Xcode project from your package.
Last but not least, before you migrate, make sure you are aware of all limitations.