SPM – Sharing Dependencies

Posted on Posted in iOS

Swift Package Manager (SPM) has limitations when it comes to importing external code into Package.swift files, making it challenging to manage dependencies in a modularized application. As a result, developers often find themselves duplicating declarations of dependencies or even duplicating version requirements, which can be quite cumbersome.

For example, if you use Swinject in multiple modules, you need to include its declaration in each module, making it even more complicated when you want to update the version. While you could use search and replace to handle this, it would be much better to have a centralized location for common dependencies.

Solution


While there is no built-in feature in SPM to achieve this, because the code in Package.swift can’t be shared, there is a simple workaround that I recently figured out.

The key insight is that by declaring a Package as a dependency in the Package.swift, you can also import all its dependencies.

With this knowledge, you can create a separate package, let’s call it “Core”, where you keep shared models, protocols, and globally shared dependencies such as Dependency Injection (DI) management. Other packages can then depend on the “Core”, effectively sharing its dependencies.

To take it a step further, you can create empty packages that aggregate dependencies into bundles, like “SharedDependencies”, “DebugTools”, “AnalyticsDependencies”, etc.

However, keep in mind that it all depends on your architecture. You need to decide whether it is worth it or not in your specific case.

Sample Code


Below you can see how this solution could be applied in practice:

This way, you can access from BusinessLogic all dependencies declared in SharedDependencies and you don’t have to duplicate all those declarations. You can simply call import Swinject or import BigInt wherever you need it.

This is something similar to sharing dependencies between targets in Podfile while using CocoaPods:

Full Source Code

You can check out a sample project here: github.com/wojciech-kulik/SPM-Sharing-Dependencies.

Things to Consider


While this approach can simplify dependency management, it’s essential to avoid spreading 3rd party dependencies across the entire project. Instead, it’s better to introduce abstractions.

For instance, if you decide to use Alamofire, limit its usage to a single module and introduce a NetworkingProvider to handle HTTP requests. Similarly, if you opt for Kingfisher, implement an ImageView that preconfigures Kingfisher and introduces some parameters if the additional configuration is needed.

The general rule is to limit the import of external dependencies to specific modules, avoiding excessive reliance on shared dependencies. However, some exceptions like Swinject might exist if you decide to use it for the entire DI management in the application. In this case, those “bundles” may become useful.

Works with Local Packages


This solution doesn’t have to be limited only to 3rd party libraries. You can also bundle your local packages into groups for easier management.

Let’s say that in your architecture you create a package per REST service. Most likely all your packages will use the same dependencies. In this case, you could try to apply this solution and create an ApiServiceDependencies bundle or something similar.