Video #18: Dependency Injection Made Comfortable
Episode: Video #18 Date: Jun 4, 2018 Access: Members Only 🔒 URL: https://www.pointfree.co/episodes/ep18-dependency-injection-made-comfortable

Description
Let’s have some fun with the “environment” form of dependency injection we previously explored. We’re going to extract out a few more dependencies, strengthen our mocks, and use our Overture library to make manipulating the environment friendlier.
Video
Cloudflare Stream video ID: a1749f75072994c754eb456639028f78 Local file: video_18_dependency-injection-made-comfortable.mp4 *(download with --video 18)*
References
- Discussions
- Overture
- Structure and Interpretation of Swift Programs
- Colin Barrett
- Functional Swift 2015
- How to Control the World
- 0018-environment-pt2
- Brandon Williams
- Stephen Celis
- Mastodon
- GitHub
- CC BY-NC-SA 4.0
- source code
- MIT License
Transcript
— 0:05
A few episodes ago we covered dependency injection . We saw that dependencies are things that “speak” to the outside world and because of this are full of side effects, which makes our applications complex. We covered the most popular way of handling dependencies in Swift, which is to put a protocol in front of it, explicitly inject it into initializers of objects that want those dependencies, and then you’re free to plug in a mock version that serves mock data.
— 0:38
We found that this approach has a pretty big problem. It adds a lot of boilerplate. Every dependency added required at least five or six places that needed to be updated, including mocks, initializers, call sites, etc. We proposed a solution that might make some folks feel a little uncomfortable: to reign in all those global singletons and pack them into one single singleton that we called the “current environment”. We saw that this approach cleaned up that boilerplate we saw with the protocol-based approach: new dependencies required very few changes. This approach is also very simple and could be introduced to a code base easily, today, without many changes!
— 1:30
Still, we know how strange this approach can seem at first glance, and we don’t want people to be uncomfortable with it. We want people to see just how simple it is. We’ve used it in our code bases many times.
— 1:41
This episode is just going to be about getting comfortable with it. Let’s relax, step through the app we were building last time, extract out a few more dependencies, and explore how setters can help us. Recap
— 2:02
Let’s start by looking at the code we ended up with last time. It’s a simple screen that shows a table view of Point-Free GitHub repos, fetched via the GitHub API.
— 2:19
The code consists of a few organized sections, and we’ve cleaned it up a bit with comments that break each section of code. First, we have a GitHub client, which is responsible for talking to GitHub. // ======================================= // // GitHub Client // // =======================================
— 2:29
We ended up with something pretty simple! A struct that wraps a couple things: a Repo model and a property, fetchRepos . struct GitHub { struct Repo: Decodable { var archived: Bool var description: String? var htmlUrl: URL var name: String var pushedAt: Date? } var fetchRepos = fetchRepos(onComplete:) }
— 2:47
The fetchRepos property, by default, is the following private implementation, which hits the live GitHub API using a URLSession and contains all the private implementation details of hitting GitHub. private func fetchRepos( onComplete completionHandler: @escaping (Result<[GitHub.Repo], Error>) -> Void ) { dataTask( "orgs/pointfreeco/repos", completionHandler: completionHandler ) } private func dataTask<T: Decodable>( _ path: String, completionHandler: @escaping (Result<T, Error>) -> Void ) { let request = URLRequest( url: URL(string: "https://api.github.com/" + path)! ) URLSession.shared.dataTask( with: request ) { data, urlResponse, error in do { if let error = error { throw error } else if let data = data { let formatter = DateFormatter() formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZZZZZ" formatter.locale = Locale(identifier: "en_US_POSIX") formatter.timeZone = TimeZone(secondsFromGMT: 0) let decoder = JSONDecoder() decoder.dateDecodingStrategy = .formatted(formatter) decoder.keyDecodingStrategy = .convertFromSnakeCase completionHandler( .success(try decoder.decode(T.self, from: data)) ) } else { fatalError() } } catch let finalError { completionHandler(.failure(finalError)) } } .resume() }
— 3:09
We also have an analytics client. // ======================================= // // Analytics Client // // ======================================= It’s very similar to the GitHub client. It’s a struct that wraps an Event model with a static helper to build tappedRepo events, and a track property. struct Analytics { struct Event { var name: String var properties: [String: String] static func tappedRepo(_ repo: GitHub.Repo) -> Event { return Event( name: "tapped_repo", properties: [ "repo_name": repo.name, "build": Bundle.main.object( forInfoDictionaryKey: "CFBundleVersion" ) as? String ?? "Unknown", "release": Bundle.main.object( forInfoDictionaryKey: "CFBundleShortVersionString" ) as? String ?? "Unknown", "screen_height": String( describing: UIScreen.main.bounds.height ), "screen_width": String( describing: UIScreen.main.bounds.width ), "system_name": UIDevice.current.systemName, "system_version": UIDevice.current.systemVersion, ] ) } } var track = track(_:) } private func track(_ event: Analytics.Event) { print("Tracked", event) } As with our GitHub client, track defaults to a private implementation. For now we’re just passing the event to Swift’s print function, but we can imagine replacing it with an implementation that hits a real analytics service.
— 4:07
Below this is our entryway point into our dependency injection “framework”. // ======================================= // // Environment // // ======================================= It’s just a struct that wraps up all our dependencies, using live implementations by default. struct Environment { var analytics = Analytics() var date: () -> Date = Date.init var gitHub = GitHub() }
— 4:22
This includes not only GitHub and Analytics , but a Date initializer, as we’ve seen that Date.init is indeed a side effect, always producing a new value as time marches on.
— 4:32
Then we created a global, Current with a capital C, instance of Environment . var Current = Environment()
— 4:38
This is the default, live version of the environment, which can reach the outside world. All access to our dependencies goes through Current . Our application shouldn’t create GitHub clients, or any of these other dependencies, outside of Environment , and it should always access these dependencies through Current .
— 4:55
Finally we have our view controller. // ======================================= // // Table View Controller // // ======================================= It’s responsible for the data source driving our table view. class ReposViewController: UITableViewController { var repos: [GitHub.Repo] = [] { didSet { self.tableView.reloadData() } } It loads its data on viewDidLoad , using the Current.gitHub client. override func viewDidLoad() { super.viewDidLoad() self.title = "Point-Free Repos" self.view.backgroundColor = .white Current.gitHub.fetchRepos { [weak self] result in DispatchQueue.main.async { switch result { case let .success(repos): self?.repos = repos .filter { !$0.archived } .sorted(by: { guard let lhs = $0.pushedAt, let rhs = $1.pushedAt else { return false } return lhs > rhs }) case let .failure(error): let alert = UIAlertController( title: "Something went wrong", message: error.localizedDescription, preferredStyle: .alert ) self?.present(alert, animated: true, completion: nil) } } } }
— 5:14
We have a couple data source methods for the number of rows and cell configuration, making sure to use the Current.date for the relative date formatting of when a repo was last pushed to. override func tableView( _ tableView: UITableView, numberOfRowsInSection section: Int ) -> Int { return self.repos.count } override func tableView( _ tableView: UITableView, cellForRowAt indexPath: IndexPath ) -> UITableViewCell { let repo = self.repos[indexPath.row] let cell = UITableViewCell(style: .subtitle, reuseIdentifier: nil) cell.textLabel?.text = repo.name cell.detailTextLabel?.text = repo.description let dateComponentsFormatter = DateComponentsFormatter() dateComponentsFormatter.allowedUnits = [ .day, .hour, .minute, .second ] dateComponentsFormatter.maximumUnitCount = 1 dateComponentsFormatter.unitsStyle = .abbreviated let label = UILabel() if let pushedAt = repo.pushedAt { label.text = dateComponentsFormatter.string( from: pushedAt, to: Current.date() ) } label.sizeToFit() let color = repo.archived ? UIColor.gray : .black cell.textLabel?.textColor = color cell.detailTextLabel?.textColor = color label.textColor = color cell.accessoryView = label return cell }
— 5:28
And we have a delegate method for row taps, which uses the Current.analytics to track the event before presenting a web view with that repo’s GitHub page.
— 5:38
Next, we have our dependency mocks. // ======================================= // // Dependency Mocks // // ======================================= We’ve mocked out our GitHub client with fetchRepos stubbed out to execute its callback with a successful result containing a mock repo. extension GitHub { static let mock = GitHub(fetchRepos: { callback in callback( [ GitHub.Repo( archived: false, description: "Blob's blog", htmlUrl: URL(string: "https://www.pointfree.co")!, name: "Bloblog", pushedAt: Date(timeIntervalSinceReferenceDate: 547152021) ) ] ) }) }
— 5:53
Analytics is similarly mocked to track events in a slightly different manner. extension Analytics { static let mock = Analytics(track: { event in print("Mock track", event) }) }
— 6:09
And finally an environment mock, which just bundles up all our mock dependencies together, including pinning the current date to a specific, unchanging time for consistent playground and test running. extension Environment { static let mock = Environment( analytics: .mock, date: { Date(timeIntervalSinceReferenceDate: 557152051) }, gitHub: .mock, ) }
— 6:21
Finally, we set up our environment, create a view controller, and throw it into our live playground view. // ======================================= // // Live Applications // // ======================================= Current = Environment() //Current = .mock let reposViewController = ReposViewController() import PlaygroundSupport let vc = UINavigationController( rootViewController: reposViewController ) PlaygroundPage.current.liveView = vc
— 6:33
We can simply uncomment Current = .mock to sub in the mock environment. This renders our mock repos immediately. We didn’t need to hit GitHub or even need an internet connection to render this screen! A simple mock
— 6:47
OK! So that’s the tour from last time! There was a lot to cover but it’s nice how simple it all is. Let’s continue to refine things, starting by looking at our mock environment. extension Environment { static let mock = Environment( analytics: .mock, date: { Date(timeIntervalSinceReferenceDate: 557152051) }, gitHub: .mock, ) }
— 7:04
It’s really nice how we’re able to use this .mock syntax to refer to our analytics and GitHub clients. Let’s do something simple by extracting our Date in a similar way. extension Date { static let mock = Date(timeIntervalSinceReferenceDate: 557152051) }
— 7:22
Now our mock environment looks even more consistent. extension Environment { static let mock = Environment( analytics: .mock, date: { .mock }, gitHub: .mock, ) }
— 7:25
It’s now incredibly clear that a mock environment is just an environment fully built out of mocks.
— 7:33
Even better, now that we have a central date mock, we can use it when building other dates. For example, in our mock GitHub client, our mock GitHub repo has a date, which is currently hard-coded to a date that happens to be 116 days before the mock date. We can update our logic to be relative to the mock instead. extension GitHub { static let mock = GitHub(fetchRepos: { callback in callback( [ GitHub.Repo( archived: false, description: "Blob's blog", htmlUrl: URL(string: "https://www.pointfree.co")!, name: "Bloblog", pushedAt: .mock - 60*60*24*116 ) ] ) }) }
— 7:57
It all still builds and renders the same, but it’s a bit nicer to read and use. Now, any time we are dealing with dates in our mocks, we can start with our base date mock.
— 8:05
Our GitHub mock is looking pretty messy, though! We invoke a callback with a successful result of an array of repos. Let’s clean this up by first extracting out that mock repo into the Repo type itself. extension GitHub.Repo { static let mock = GitHub.Repo( archived: false, description: "Blob's blog", htmlUrl: URL(string: "https://www.pointfree.co")!, name: "Bloblog", pushedAt: .mock - 60*60*24*116 ) } And now, anytime anyone needs to reach for a GitHub repo, they can reach for one on the type itself. This includes the client! extension GitHub { static let mock = GitHub(fetchRepos: { callback in callback([.mock]) }) }
— 8:51
Our mocks are starting to look nice and clean. Mocking made easy
— 8:54
Let’s try something else out. Right now we’re only getting a single mock repo back from our mock client, but it seems like getting an array of repos would be more useful in our testing.
— 9:05
Something cool we can do in Swift is use a constrained extension on Array where the element is a GitHub repo. extension Array where Element == GitHub.Repo {}
— 9:16
This means we can add a static mock that returns an array of repos. extension Array where Element == GitHub.Repo { static let mock = [ GitHub.Repo.mock, ] } This provides a very slight cleanup to our client, where we can replace [.mock] with .mock . extension GitHub { static let mock = GitHub(fetchRepos: { callback in callback(.mock) }) }
— 9:30
Now, when we update our array mock, we start to see multiple repos come back in our live view. extension Array where Element == GitHub.Repo { static let mock = [ GitHub.Repo.mock, .mock ] } Rendering the same repo twice doesn’t seem like a real world use case, though, so let’s return a few different mock repos instead.
— 9:43
Let’s use Overture , which provides a helpful collection of setter functions that should make the experience a bit nicer. import Overture
— 9:57
Now we can update our mock repos, in place, to change the name of the second repo using with and set . extension Array where Element == GitHub.Repo { static let mock = [ GitHub.Repo.mock, with(.mock, set(\.name, "Nomadic Blob")) ] } It’s nice to see some variety in the name, so let’s add some more variety.
— 10:17
We can further update our mock using concat , which allows us to compose multiple changes together. extension Array where Element == GitHub.Repo { static let mock = [ GitHub.Repo.mock, with(.mock, concat( set(\.name, "Nomadic Blob"), set(\.description, "Where in the world is Blob?"), set(\GitHub.Repo.pushedAt, .mock - 60*60*24*2) )) ] } We used an unabbreviated key path for that last one because pushedAt is an optional Date? , and the type checker was having a bit of trouble inferring things.
— 11:00
This is really cool! Let’s define another helper in this array extension that spits out any number of specified repos. static func mocks(_ count: Int) -> Array { return (1...count).map { n in with(.mock, concat( over(\.name) { "#\(n): \($0)" }, set(\GitHub.Repo.pushedAt, .mock - 60*60*24*TimeInterval(n)) )) } }
— 11:51
We’re free to use this logic anywhere! We could even tack on a few extra repos to our default mock. static let mock = [ GitHub.Repo.mock, with(.mock, concat( set(\.name, "Nomadic Blob"), set(\.description, "Where in the world is Blob?"), set(\GitHub.Repo.pushedAt, .mock - 60*60*24*2) )) ] + mocks(5)
— 11:57
We can now simulate 5, 50, or however many blogs we want using a simple function!
— 12:09
This is all a simple process: we keep building small, static, reusable helpers that let us compose different mock data onto the screen. With each type we can start with “one true mock” and then, using Overture, easily derive new mocks from it! Refactoring made easy
— 12:51
Let’s keep going and make another change. Right now we’re filtering out archived repos, but it’d be nice bring them back and style them just a little bit differently.
— 13:07
To begin with, let’s include an archived repo in our mock list. static let mock = [ GitHub.Repo.mock, with(GitHub.Repo.mock, set(\.archived, true)) with(.mock, concat( set(\.name, "Nomadic Blob"), set(\.description, "Where in the world is Blob?"), set(\GitHub.Repo.pushedAt, .mock - 60*60*24*2) )) ] + mocks(5)
— 13:18
Nothing showed up, so let’s make sure we comment out that filtering logic. // .filter { !$0.archived }
— 13:31
A new blog showed up, but there’s not much to distinguish it from the other default mock. Let’s update our data source method to configure cells of archived repos a bit differently. let color = repo.archived ? UIColor.gray : .black cell.textLabel?.textColor = color cell.detailTextLabel?.textColor = color label.textColor = color
— 14:12
Now the archived repo appears in a grayed-out state! It’s fun how easy it was to make these changes in a playground and immediately see them happen. Our tools really make a difference here. Our mock repo made it easy to add a new row to the table view. Overture made it easy to flip its archived state. Simulating this kind of data in any other kind of environment would have been a lot tougher, but here we’re free to explore any wild edge cases we see fit. More hidden dependencies?
— 14:39
There are a few more dependencies hidden throughout this screen. Let’s take another look at our analytics client. struct Analytics { struct Event { var name: String var properties: [String: String] static func tappedRepo(_ repo: GitHub.Repo) -> Event { return Event( name: "tapped_repo", properties: [ "repo_name": repo.name, "build": Bundle.main.object( forInfoDictionaryKey: "CFBundleVersion" ) as? String ?? "Unknown", "release": Bundle.main.object( forInfoDictionaryKey: "CFBundleShortVersionString" ) as? String ?? "Unknown", "screen_height": String( describing: UIScreen.main.bounds.height ), "screen_width": String( describing: UIScreen.main.bounds.width ), "system_name": UIDevice.current.systemName, "system_version": UIDevice.current.systemVersion, ] ) } } var track = track(_:) }
— 14:39
That tappedRepo function has a lot of dependencies. Whenever we call it, we’re reaching to the outside world through a bunch of singletons: Bundle.main , UIScreen.main , and UIDevice.current .
— 15:02
Let’s extract them to our environment, and let’s follow the examples of our analytics and GitHub clients by using structs.
— 15:21
First, we have those Bundle.main dependencies. All we’re doing is plucking out two version strings. We can house these in a new struct. struct Version { var build = Bundle.main.object( forInfoDictionaryKey: "CFBundleVersion" ) as? String ?? "Unknown" var release = Bundle.main.object( forInfoDictionaryKey: "CFBundleShortVersionString" ) as? String ?? "Unknown" }
— 16:03
By default it uses a live implementation that reaches into Bundle.main for its data.
— 16:13
Next, we have a couple UIScreen.main dependencies, which we can control with another struct. struct Screen { var screenHeight = String(describing: UIScreen.main.bounds.height) var screenWidth = String(describing: UIScreen.main.bounds.width) }
— 16:35
One more! UIDevice.current dependencies. Let’s extract it to another struct! struct Device { var systemName = UIDevice.current.systemName var systemVersion = UIDevice.current.systemVersion }
— 17:00
We now need to add these dependencies to our environment, which is as simple as adding each as a property with a default. struct Environment { var analytics = Analytics() var date: () -> Date = Date.init var device = Device() var gitHub = GitHub() var screen = Screen() var version = Version() } We only have one compile error here, and it’s with our mock environment. Let’s update it to use the live versions for now to get everything building again, and we’ll handle the mocks in a moment. extension Environment { static let mock = Environment( analytics: .mock, calendar: .mock, date: { .mock }, device: Device(), gitHub: .mock, screen: Screen(), version: Version() ) }
— 17:31
Things are compiling again, but we aren’t actually using these new dependencies. Let’s update our analytics event. return Event( name: "tapped_repo", properties: [ "repo_name": repo.name, "build": Current.version.build, "release": Current.version.release, "screen_height": Current.screen.screenHeight, "screen_width": Current.screen.screenWidth, "system_name": Current.device.systemName, "system_version": Current.device.systemVersion, ] )
— 18:08
Everything builds and our events are tracking just as they had before, but we’re currently using our mock environment, so we should be controlling these side effects with mocks. extension Device { static let mock = Device( systemName: "Mock iOS", systemVersion: "11.mock" ) } extension Screen { static let mock = Screen(screenHeight: "568", screenWidth: "376") } extension Version { static let mock = Version(build: "42", release: "0.0.1") }
— 19:30
And now we just swap them into our mock environment. extension Environment { static let mock = Environment( analytics: .mock, date: { .mock }, device: .mock, gitHub: .mock, screen: .mock, version: .mock ) }
— 19:38
Now, when we track an event, we see the mock versions of our bundle, screen, and system data.
— 19:51
Let’s take a moment to appreciate how easy that was. We identified three separate dependencies in our analytics tracking code, extracted them to structs that represent each dependency, and we defaulted all their fields to the live versions. We added those dependencies to Environment and only got a single compile error: the mock environment needed to be updated to specify them. That may not have even been a compile error in an app, since the mock may have been living in the test target. Fixing that single error allowed us to immediately use these dependencies via Current . There’s no friction to this experience.
— 20:45
From there, we quickly created mock values for each dependency and updated our mock environment to use them. That’s all it took to fully control these dependencies. We even got a nice benefit from controlling these specific dependencies! Our Version struct, for example, hides a less safe interface that takes string keys with a safer, static, auto-completable interface.
— 21:18
Let’s recall what it would have taken to control these dependencies using protocols. Every dependency we controlled led to five or six compile errors, so we’d be dealing with 15+ in this case just to get things building. And these include application target errors! The one error we dealt with can be isolated to a test target. Protocol-based dependency injection has a lot of friction and can stop your development pace in its tracks. Another hidden dependency?
— 21:45
There’s another dependency we haven’t controlled, hiding in plain sight: the date components formatter our cell uses to format a relative date string. let dateComponentsFormatter = DateComponentsFormatter() dateComponentsFormatter.allowedUnits = [ .day, .hour, .minute, .second ] dateComponentsFormatter.maximumUnitCount = 1 dateComponentsFormatter.unitsStyle = .abbreviated This is because the formatter has a calendar property that defaults to the calendar that the device is currently set to. There are many cases where singletons are hiding inside other structures.
— 22:09
In fact, Calendar hides another singleton: Locale !
— 22:16
Let’s take control of the calendar. We just need to add a single line to Environment . struct Environment { var analytics = Analytics() var calendar = Calendar.autoupdatingCurrent var date: () -> Date = Date.init var device = Device() var gitHub = GitHub() var screen = Screen() var version = Version() }
— 22:30
We have one compile error, we already know what it is. Let’s update our mock. For now we can get things building by using the same, live version. extension Environment { static let mock = Environment( analytics: .mock, calendar: Calendar.autoupdatingCurrent, date: { .mock }, device: .mock, gitHub: .mock, screen: .mock, version: .mock ) }
— 22:43
That builds, so we can update our formatter to use it! let dateComponentsFormatter = DateComponentsFormatter() dateComponentsFormatter.calendar = Current.calendar dateComponentsFormatter.allowedUnits = [ .day, .hour, .minute, .second ] dateComponentsFormatter.maximumUnitCount = 1 dateComponentsFormatter.unitsStyle = .abbreviated
— 22:57
Let’s fix our mock environment to use a controlled calendar. We’ll start by extending Calendar with a static mock property. extension Calendar { static let mock = Calendar(identifier: .gregorian) }
— 23:19
And we can update our mock environment to use it. extension Environment { static let mock = Environment( analytics: .mock, calendar: .mock, date: { .mock }, device: .mock, gitHub: .mock, screen: .mock, version: .mock ) }
— 23:23
So what does mocking the calendar out buy us? We can use Overture’s mutating setters to modify our playground environment in place by swapping out our default mock calendar with another one. with(&Current, mut(\.calendar.locale, Locale(identifier: "zh_HK")))
— 23:50
Alright, we’re seeing our table view rendered in a different way! And we were able to do so immediately, with a single line of code!
— 24:15
Let’s update our repo mocks for more variety. It’d be nice to see units other than days in the table view. Let’s update one of our mocks to have been pushed to just a couple hours ago.
— 24:24
And now we’re seeing a change in the format of that row! We could keep going and simulate other intervals, but it’s worth just appreciating how powerful and easy this all was!
— 24:42
There’s other behavior hidden in this formatter, and that’s the way numbers are formatted. To see this, we’ll need to change our mocks a bit more. Let’s ensure some repos that haven’t been updated in ages show up. static func mocks(_ count: Int) -> Array { return (1...count).map { n in with(.mock, concat( over(\.name) { "#\(n): \($0)" }, set( \GitHub.Repo.pushedAt, .mock - 60*60*24*TimeInterval(n)*1000 ) )) } }
— 25:05
Right now, commas delimit the thousands place, but we can change the calendar locale and immediately see a different format. with(&Current, mut(\.calendar.locale, Locale(identifier: "de_DE"))) And instantly, we see a dot delimiting the thousands place.
— 25:27
Now, we said we controlled all of our dependencies, but this mutation we’re doing on Current.calendar is a little sneaky. We’re reaching into its locale , something that we didn’t control when we first made our mock. Let’s use Overture to correct this, and while we’re in here, let’s add a mock to Locale . extension Locale { static let mock = Locale(identifier: "en_US") } extension Calendar { static let mock = with( Calendar(identifier: .gregorian), set(\.locale, .mock) ) }
— 26:10
And with that, we have a default, controlled calendar. This view controller is now fully controlled.
— 26:32
The additional dependencies we controlled today are subtle and easy to miss! And it’s rare that we control all of them: calendar, locale and language, device and bundle information. Environment lets us control them all easily! When we don’t control these dependencies, simulating other languages and locales is extremely cumbersome, usually requiring us to modify a simulator or device’s settings, and wait for a reboot. This kind of friction may have prevented us from bothering to even check these things.
— 27:19
Adopting Environment has a real impact on development pace and our ability to simulate just about anything. What’s the point?
— 27:31
These kinds of episodes are dedicated to “the point”. The entire episode is constantly focused on “the point”. We hope that in demonstrating how easy it is to work with Environment , that our viewers will embrace this style of dependency injection. It’s incredibly lightweight with little to no ceremony. We’ve controlled eight dependencies so far and we didn’t have to mess with any protocols, conformances, initializers, and so on. Here we use simple structs that describe our dependencies as properties that we can swap out freely. References Structure and Interpretation of Swift Programs Colin Barrett • Dec 15, 2015 Colin Barrett discussed the problems of dependency injection, the upsides of singletons, and introduced the Environment construct at Functional Swift 2015 . This was the talk that first inspired us to test this construct at Kickstarter and refine it over the years and many other code bases. https://www.youtube.com/watch?v=V-YvI83QdMs How to Control the World Stephen Celis • Sep 24, 2018 Stephen gave a talk on our Environment -based approach to dependency injection at NSSpain 2018. He starts with the basics and slowly builds up to controlling more and more complex dependencies. https://vimeo.com/291588126 Downloads Sample code 0018-environment-pt2 Point-Free A hub for advanced Swift programming. Brought to you by Brandon Williams and Stephen Celis . Content Become a member The Point-Free Way Beta previews Gifts Videos Collections Free clips Blog More About Us Community Slack Mastodon Twitter BlueSky GitHub Contact Us Privacy Policy © 2026 Point-Free, Inc. All rights are reserved for the videos and transcripts on this site. All other content is licensed under CC BY-NC-SA 4.0 , and the underlying source code to run this site is licensed under the MIT License .