This is a simple dependency injection framework good for managing global dependencies (app-level singletons basically) in small and medium-sized apps. Its earlier versions have been used successfully in several small personal projects and a mid-sized commercially released app.
It's meant to add as little friction as possible to the task of injecting dependencies so early stage apps can still take on the benefits of dependency injection (better testability, easier maintenance and changes in dependencies) without slowing down the pace of development.
As of version 2 and up, the package leans heavily on the use of Swift macros so support for Xcode 15 and the Swift 5.9 toolchain or newer are required.
GlobalDependencies doesn't have much in the way of dependencies itself so it can work in whatever the tools support.
- Xcode 15.1 or later.
- Swift 5.9 or later.
- iOS 13 or later.
- macOS Catalyst 13 or later (but why?).
- macOS 10.15 or later.
- tvOS 13 or later.
- watchOS 6 or later.
GlobalDependencies is currently only supported through Swift Package Manager.
If developing your own package, you can add the following lines to your Package.swift
file:
dependencies: [
.package(url: "https://github.com/Gabardone/GlobalDependencies", from: "2.0.0"),
]
To add to an Xcode project, paste https://github.com/Gabardone/GlobalDependencies
into the URL field for a new package
and specify "Up to next major version" starting with the current one.
The package is fully documented, including tutorials for expected use.
You can find an online version of the package's DocC documentation here.
When developing on Xcode with the dependency installed you can also select Product -> Build Documentation
which will
add the package's documentation to the contents of the Xcode documentation viewer.
It may look like a lot of steps but in reality it's a few minutes work overall, and once it's set up further work has negligible further friction. For more detailed explanation look in the official documentation for the package.
-
Declare a protocol that vends the API from your dependency. We'll call it the API protocol (
MyAPIProtocol
as an example through this quick start). -
Make sure there's a default implementation of the protocol that will be the one used by default by your running code. For convenience name it either by itself or through
typealias
something likeDefaultMyAPIProtocol
. -
Add the GlobalDependencies package to your project dependencies,
import GlobalDependencies
where your API protocol is declared. -
Ensure your default implementation of your API protocol adopts the
DefaultDependencyValueFactory
protocol returning a value that is compatible withany MyAPIProtocol
-
Attach the
@Dependency
macro to your API protocol as follows:
@Dependency
protocol MyDependencyProtocol {
/* ... */
}
- Extend
GlobalDepedencies
to adoptMyAPIProtocol.Dependency
as follows:
extension GlobalDependencies: MyAPIProtocol.Dependency {
#GlobalDependency(type: MyAPIProtocol)
}
- On whichever types you want to inject that dependency attach the
@InjectedDependency
macro withMyAPIProtocol
as one of its parameters
@InjectedDependencies(MyAPIProtocol)
class MyComponent {
/* ... */
}
- You'll want to add a
Dependencies
parameter to your component's initializer.
@InjectedDependencies(MyAPIProtocol)
class MyComponent {
init(/* more parameters */, dependencies: Dependencies) {
self.dependencies = dependencies
/* ... */
}
/* ... */
}
- Access your injected dependency just by extracting it from your component's
dependencies
@InjectedDependencies(MyAPIProtocol)
class MyComponent {
/* ... */
func doAThing() {
/* ... */
dependencies.myAPIProtocol.doSomething()
/* ... */
}
/* ... */
}
In addition to the base GlobalDependencies package itself, we are building as needed a number of common system framework façade dependencies that can be quickly and easily adopted to abstract away common hard dependencies and make the resulting logic far more testable than otherwise.
These are mostly tiny so we call them depdendency micropackages (ok, the jokes write themselves). It's easy enough to
find those we built ourselves by just looking at the repo list for the account and checking those that are named
*Dependency
.
Since those dependency micropackages are built up as needed by other projects they are usually not going to implement a complete wrap of the abstracted API (unless all of it was needed at some point). Feel free to fork and expand if needed, and please bring back a PR with any additions you make that may be useful to others.
While the GlobalDependencies package itself works "as-is" and is unlikely to get major changes going forward, I know better than to think it can't be improved, so suggestions are welcome especially if they come with examples.
The dependency micropackages can always use more work as they are built as needed. If you find yourself using one but improving it, feel free to submit a PR with the changes. Same if you build a generic one for a system service that other people could stand to use. I'll happily point to other people's dependency packages if anyone ends up creating any.
Beyond that just take to heart the baseline rules presented in contributing guidelines prior to submitting a Pull Request.
Thanks, and happy low friction dependency injection!
Double-click on Package.swift
in the root of the repository to open the project in Xcode. Or open the containing
folder from Xcode (from the command line: open -a Xcode <path to package folder>
works as well).