EP 16 · Standalone · May 21, 2018 ·Members

Video #16: Dependency Injection Made Easy

smart_display

Loading stream…

Video #16: Dependency Injection Made Easy

Episode: Video #16 Date: May 21, 2018 Access: Members Only 🔒 URL: https://www.pointfree.co/episodes/ep16-dependency-injection-made-easy

Episode thumbnail

Description

Today we’re going to control the world! Well, dependencies to the outside world, at least. We’ll define the “dependency injection” problem and show a lightweight solution that can be implemented in your code base with little work and no third party library.

Video

Cloudflare Stream video ID: 81951e9c8bd9f0b2ebe2aa5b2dc7747b Local file: video_16_dependency-injection-made-easy.mp4 *(download with --video 16)*

References

Transcript

0:05

Today we’re going to revisit a topic we’ve talked about in the past: side effects. We introduced the topic in our second episode with a deep dive on how side effects are kind of like hidden inputs and outputs to our functions, demonstrating how they make testing difficult and how they can introduce subtle bugs. Side effects are complicated and require attention.

0:31

The examples in that episode were a bit contrived, so today we’re going to look at code that’s a bit more real-world and explore how we can extract and control side effects quickly and painlessly.

0:51

This is a method we’ve used in production again and again, and it’s proven itself over time. We hope that others get the same benefit! An example application

1:03

Here’s an example app: a table view of Point-Free repos fetched live from GitHub. The code defines a GitHub API client, a nested Repo model, and the business logic required to fetch repos from the GitHub API. struct GitHub { struct Repo: Decodable { var archived: Bool var description: String? var htmlUrl: URL var name: String var pushedAt: Date? } func fetchRepos( onComplete completionHandler: @escaping (Result<[GitHub.Repo], Error>) -> Void ) { self.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() } }

1:54

We also have an analytics client, which nests an Event struct that describes common things we want to know, like how many folks have upgraded to the latest iOS. 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, ] ) } } private func track(_ event: Analytics.Event) { print("Tracked", event) } }

2:07

Our event tracking function just prints to the console, but we’d substitute this with another networking event when our app ships.

2:16

Finally we have a ReposViewController , a table view controller that requests repos from our GitHub client on viewDidLoad , massages the data, and populates our table view data source with results or displays an alert on failure. class ReposViewController: UITableViewController { var repos: [GitHub.Repo] = [] { didSet { self.tableView.reloadData() } } override func viewDidLoad() { super.viewDidLoad() self.title = "Point-Free Repos" self.view.backgroundColor = .white 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) } } } } 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: Date()) } label.sizeToFit() cell.accessoryView = label return cell } override func tableView( _ tableView: UITableView, didSelectRowAt indexPath: IndexPath ) { let repo = self.repos[indexPath.row] Analytics().track(.tappedRepo(repo)) let vc = SFSafariViewController(url: repo.htmlUrl) self.present(vc, animated: true, completion: nil) } }

2:40

Our table view controller is simple and both configures its own cells, to show the repo information and a relative date for when it was last updated, and handles its own cell actions, where when a cell is selected, we track the tap using our analytics service and push on a new view controller.

3:26

This code is a reasonable first step to building any screen in an application, but we want to improve it, what are some first steps?

4:05

Let’s focus on the side effects, like the analytics event and the GitHub API request. We want control these effects because they’re currently controlling us and how we can run our apps. As it stands this app cannot run without an active internet connection, and so without an active internet connection we can’t work on this app. Dependency injection

4:25

A common way to approach this problem is to use “dependency injection”: we can extract our dependencies on the outside world, inject them into our code, and then later we can also inject mock dependencies.

4:37

It’s common to address dependency injection in Swift using protocol-oriented programming. Let’s look at what that looks like.

4:47

Usually when we want to control a dependency, we conform it to a protocol. We can create a GitHubProtocol that describes our dependency on GitHub: protocol GitHubProtocol { func fetchRepos( onComplete completionHandler: @escaping (Result<[GitHub.Repo], Error>) -> Void ) }

5:05

All we need to do is conform our GitHub client to GitHubProtocol , and everything still builds. struct GitHub: GitHubProtocol { … }

5:13

With a protocol, we need something that cares about it. Our repos view controller currently instantiates our client and calls out to it directly, but now we need to instead inject it with this dependency. Let’s do so at initialization. class ReposViewController: UITableViewController { let gitHub: GitHubProtocol init(gitHub: GitHubProtocol) { self.gitHub = gitHub super.init(nibName: nil, bundle: nil) } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } … } We make sure to refer to the type as GitHubProtocol so that we can inject another mock type in the future. We also have to add boilerplate for a required NSCoding initializer NSCoding .

5:56

Now our view controller uses self.gitHub instead of initializing a client itself, and we instead pass a GitHub client to our view controller on initialization. let reposViewController = ReposViewController(gitHub: GitHub())

6:10

That’s all that’s required for everything to still work! This is all that “dependency injection” is! GitHub is a dependency: we don’t control it, so we use a client to interact with it. Rather than allow our view controller to pluck a live GitHub client out of thin air, we’ve given it a property and allowed it to be injected with this dependency on initialization.

6:36

Now we just need something else that conforms to GitHubProtocol , and we can pass it along just as easily.

6:44

Let’s create a mock version of our GitHub client and conform it to this protocol. struct GitHubMock: GitHubProtocol { func fetchRepos( onComplete completionHandler: @escaping (Result<[GitHub.Repo], Error>) -> Void ) { completionHandler( .success( GitHub.Repo( archived: false, description: "Blob's blog", htmlUrl: URL("https://www.pointfree.co"), name: "Bloblog", pushedAt: Date(timeIntervalSinceReferenceData: 547000000) ) ) ) } }

7:29

We’re merely returning a successful payload to the completion handler with a mock repo.

7:38

GitHubMock conforms to GitHubProtocol , so we can swap it our when we instantiate our repos view controller. let reposViewController = ReposViewController(gitHub: GitHubMock())

7:46

Everything compiled and ran! We aren’t making a network request to GitHub and don’t have to worry if we have a network connection or if GitHub is down. That mock data we quickly created is also being displayed, which means we can modify and see how our app responds!

8:00

This wasn’t a lot of code but we’ve opened a lot of doors, and this is exactly what “dependency injection” means. The idea can seem scary and a lot more complicated: there are extremely large frameworks built around the idea. Dependency injection boils down to controlling side effects: you identify a dependency you can’t control and wrap it in a context that you can control.

8:27

We are also now allowed to take our mock value, make a change to it, and then see how it changes the app. For example, flipping our mock value to be an archived repo removes it from the table view. We can see this instantly with very little work. We didn’t have to go to our GitHub org, create a new repo, and then archive it just so that we could see how this works.

8:50

Swift has something that makes this kind of dependency injection a bit nicer: default arguments. Let’s look at where we inject our dependency: the view controller initializer. We can use a default argument to assign the live GitHub client. init(gitHub: GitHubProtocol = GitHub()) { … }

9:17

Everything still works, but we no longer have to inject our dependency manually in our app. We can instantiate our view controller without the gitHub argument. let reposViewController = ReposViewController()

9:28

This is nice because everyday code doesn’t need to even worry about dependency injection, it just defaults to the live code. Controlling time

9:40

Let’s control another effect. It’s subtle, and we’ve covered it before : it’s the Date initializer that returns the current date. We’re using it to compute the relative date for when a repo was last pushed to. This initializer returns a different value every time it’s called. We want to control this to make testing easier and to make our interface more predictable.

10:16

Let’s inject this as a dependency. Computing the date is a dependency, and we can inject it by directly handing our view controller the function that returns the current date. We can even hand it Date.init as a default. class ReposViewController: UITableViewController { let date: () -> Date let gitHub: GitHubProtocol init( date: @escaping () -> Date = Date.init, gitHub: GitHubProtocol = GitHub() ) { self.date = date self.gitHub = gitHub super.init(nibName: nil, bundle: nil) } … }

10:47

This runs, but we’re not using it, we need to swap out Date() for self.date() . label.text = dateComponentsFormatter.string(from: pushedAt, to: self.date())

11:02

And it works just the same. Let’s try something. Let’s instantiate our ReposViewController and pass it a GitHub mock where our GitHub repo was pushed to within the last minute. struct GitHubMock: GitHubProtocol { func fetchRepos( onComplete completionHandler: @escaping (Result<[GitHub.Repo], Error>) -> Void ) { completionHandler( .success( GitHub.Repo( archived: false, description: "Blob's blog", htmlUrl: URL("https://www.pointfree.co"), name: "Bloblog", pushedAt: Date(timeIntervalSinceReferenceData: 547152021) ) ) ) } } Every time we load this screen our label has different text as time passes.

11:43

Let’s also pass a date-providing function when we instantiate our ReposViewController that passes a consistent date. let reposViewController = ReposViewController( date: { Date(timeIntervalSince(547152051) }, gitHub: GitHubMock() ) We provided a date thirty seconds after the date we gave our mock, and we consistently see, each time we load this screen, the same “30s” result.

12:09

We now have a way to completely, predictably render this screen! We’re not beholden to GitHub or the current date. We just provide these as dependencies and, given the same dependencies we can expect the same result every time. This means predictable screen shots for iTunes connect, screen shot tests, and more!

12:44

By controlling these inputs we can test edge cases that would have been otherwise difficult. We can fast-forward today’s date and see how it looks like for a repo to not have been pushed to in awhile. let reposViewController = ReposViewController( date: { Date(timeIntervalSince(557152051) }, gitHub: GitHubMock() )

12:59

We’ve gotten a lot of power out of this simple system, but it has some significant shortcomings: it’s just not super flexible. Let’s look at our GitHub mock: we’ve hardcoded its implementation of fetchRepos to return the happy path. If we want to test our failure, we need to swap this logic out. struct GitHubMock: GitHubProtocol { func fetchRepos( onComplete completionHandler: @escaping (Result<[GitHub.Repo], Error>) -> Void ) { completionHandler( .failure( NSError( domain: "co.pointfree", code: -1, userInfo: [NSLocalizedDescriptionKey: "Ooops!"] ) ) // .success( // GitHub.Repo( // archived: false, // description: "Blob's blog", // htmlUrl: URL("https://www.pointfree.co"), // name: "Bloblog", // pushedAt: Date(timeIntervalSinceReferenceData: 547000000) // ) // ) ) } }

13:49

Alright, we’re seeing our failure case! It’s nice to see that our logic was correct, but this changes our GitHubMock for everyone: every test or screen that may be using the mock will be failing.

14:12

We need a way of customizing our mock so that different code can explore different states of our dependencies. One thing we can do is have our GitHubMock hold onto a property that defines what it returns from fetchRepos so that it doesn’t have to be hard-coded. struct GitHubMock: GitHubProtocol { let result: Result<[GitHub.Repo], Error> func fetchRepos( onComplete completionHandler: @escaping (Result<[GitHub.Repo], Error>) -> Void ) { completionHandler(self.result) } }

14:52

This change means that everywhere we depend on a GitHub mock we need to supply this data, so we probably want to supply a default initializer with the happy path again. struct GitHubMock: GitHubProtocol { let result: Result<[GitHub.Repo], Error> init(result: Result<[GitHub.Repo], Error> = .success( GitHub.Repo( archived: false, description: "Blob's blog", htmlUrl: URL("https://www.pointfree.co", name: "Bloblog", pushedAt: Date(timeIntervalSinceReferenceData: 547000000) ) ) { self.result = result } func fetchRepos( onComplete completionHandler: @escaping (Result<[GitHub.Repo], Error>) -> Void ) { completionHandler(self.result) } }

15:08

Everything runs, and we can sub in a failure when instantiating our mock. let reposViewController = ReposViewController( date: { Date(timeIntervalSince(557152051) }, gitHub: GitHubMock( result: .failure( .failure( NSError( domain: "co.pointfree", code: -1, userInfo: [NSLocalizedDescriptionKey: "Ooops!"] ) ) ) ) )

15:27

Now we can control our mock for success and failure cases, granularly, but it doesn’t feel quite right. We had to modify our mock type, add a property, add an initializer and a default. This was for a client with just a single endpoint so far. If we wanted to add another endpoint to our GitHub client, we have to: Add a method to GitHubProtocol so that our live and mock types can utilize it Add a method and implement it our live GitHub type Add a property to contain the result in our mock Update our mock’s initializer to customize this property Provide a default “happy path” value to this initializer Add a method to our mock to pass this property to a callback

16:31

Using protocols to solve this problem results in a lot of extra boilerplate. Let’s try to solve this problem without a protocol. Current

17:02

Let’s grab our dependencies out of our view controller and stick them into a new type. struct Environment { let date: () -> Date let gitHub: GitHubProtocol } We’ve called this context “environment”, and we can think of it as the environment surrounding our app when it runs.

17:12

Let’s provide some default, live versions of each property. struct Environment { let date: () -> Date = Date.init let gitHub: GitHubProtocol = GitHub() }

17:21

And we’re gonna make things a little more mutable by demoting let to var so that we can swap these dependencies out easily. struct Environment { var date: () -> Date = Date.init var gitHub: GitHubProtocol = GitHub() }

17:28

Now that we have this Environment type, how do we use it? This may feel strange, but we’re going to instantiate a top-level, mutable instance and call it Current , capitalized and all. var Current = Environment()

17:44

With this small bit of code, we can delete our ReposViewController initializers and swap out self.gitHub and self.date for Current.gitHub and Current.date .

18:14

We’re hitting the live API again, as is described in our Current environment. The code got simpler, with less configuration and a single dependency on Current . At the same time, viewers may be cringing or hearing alarm bells. The general dogma is that singletons are bad, and we’ve just created a mega-singleton: a singleton of singletons. We even gave this variable a strange capital letter. Why have we done this?

18:42

Environment groups all of our dependencies together, which is a nice way of centralizing them in a single place. We’ve also created a Current singleton, which feels wrong, but it’s worth exploring why singletons are maligned. Singletons are usually not testable because we have no influence on what and when they are. This prevents them from being testable and we’re forced into more convoluted methods of dependency injection, like passing protocolized versions of them at view controller initialization.

19:09

This is a singleton that solves the problem of singletons: it’s a singleton we can control and own. Because its a list of mutable properties, we’re free to substitute any of them for something else. Controlling the world

19:25

We previously passed ad hoc mocks to our view controller to test certain paths. Instead, let’s create a single mock for our entire Environment that operates on the happy path. extension Environment { static let mock = Environment( date: { Date(timeIntervalSinceReferenceDate: 557152051) }, gitHub: GitHubMock() ) }

19:52

With that, whenever we wanna sub the real world out for a mock one, we merely use a single line of assignment. Current = .mock

20:09

That’s all it took to make our view controller render our mock world! It has no knowledge of the mock and it didn’t need to have dependencies passed to it explicitly. It relies on Current and behaves accordingly. Capital Current is now a signal that “this is a side effect” and that “this is a side effect we have control over.”

20:55

We did this all in just a few lines of code! Every time we identify a new dependency we want to control, we just add a line to Environment , and a line to our mock. One less protocol

21:15

We got rid of the need to know about the GitHubProtocol in our controller and mock code, so now let’s get rid of the GitHubProtocol entirely!

21:29

In order for our GitHub client struct to know how to execute its side effects, it has to store this logic somehow. We can exchange its method for a property that holds onto a closure. The methods it uses thus far can be extracted to private, top-level functions merely by removing the one instance of self that wasn’t necessary. 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) ) { … } Now, we can assign this private fetchRepos function to our GitHub struct as a default property. struct GitHub /* : GitHubProtocol */ { struct Repo: Decodable { var archived: Bool var description: String? var htmlUrl: URL var name: String var pushedAt: Date? } var fetchRepos = fetchRepos(onComplete:) }

22:19

What we’ve done here is removed a protocol conformance and very concretely given storing properties for all the functions the protocol was exposing to us.

22:33

The private functions that execute the side effects are now implementation details that the live, default GitHub client exposes to us.

22:41

We’ve gotten rid of our GitHubProtocol , which means we need to get rid of any reference. First, our Environment is simplified to refer instead to a plain ole struct and assign it a live GitHub client by default. struct Environment { var date: () -> Date = Date.init var gitHub = GitHub() }

22:55

We also had a GitHubMock type that conformed to GitHubProtocol . Instead, let’s do the same as we did with Environment and define a static mock . extension GitHub { let static mock = GitHub(fetchRepos: { callback in callback( .success([ GitHub.Repo( archived: false, description: "Blob's blog", htmlUrl: URL("https://www.pointfree.co"), name: "Bloblog", pushedAt: Date(timeIntervalSinceReferenceData: 547152021) ) ]) ) }) }

23:34

The rest of our mock struct can be deleted.

23:40

Now are mock Environment just needs to use this mock client! extension Environment { static let mock = Environment( date: { Date(timeIntervalSinceReferenceDate: 557152051) }, gitHub: .mock ) } We’ve used dot-abbreviation to refer to the static GitHub.mock value.

23:47

That all worked! We’re substituting this mock environment in a single line. That’s it! If we comment it out, we get our live version. If we comment it back in, we get our mock version.

24:03

Because Current is mutable, we can simulate any real-world use case by making a modification directly on Current . So, if we want to see what the app looks like when GitHub is sending us back errors, let’s just directly replace Current.gitHub.fetchRepos with something that errors: Current.gitHub.fetchRepos = { callback in callback( .failure( NSError( domain: "co.pointfree", code: -1, userInfo: [NSLocalizedDescriptionKey: "Ooops!"] ) ) ) } We now have a world in which GitHub fails again, but this time we just changed Current to return this data directly. We didn’t need to instantiate a new mock GitHub client or rely on that boilerplate in any way! We just mutated one property on our GitHub environment.

24:54

It doesn’t require a lot of ceremony, now, to exercise all of our code paths, even the rare ones! We just swap out our dependencies in a very succinct fashion. We say: this is how we want to control all of our side effects.

25:07

It may seem strange to have an series on functional programming celebrate global mutation, but we should point out that the Environment we’ve defined so far shouldn’t be mutated in production. The production environment defaults to the live functions that returns the current date and hits real GitHub endpoints, and stays static for the duration of the app. However, by putting all the dependencies in one place we make it much easier to write tests, run our app in a playground, and just more generally load our application into a controlled environment with very little work. One more dependency

25:55

To see how flexible this system is, let’s add one more dependency to Environment .

26:10

The main dependency we haven’t covered is this analytics service. It emits a tracking event when we click on a repo. Let’s bring it into Environment .

26:19

We always bring new dependencies into Environment as mutable properties. We can default it to our main client. struct Environment { var analytics = Analytics() var date: () -> Date = Date.init var gitHub = GitHub() } Our Environment mock is now broken, so let’s assign our live instance, again, while we troubleshoot. extension Environment { static let mock = Environment( analytics: Analytics(), date: { Date(timeIntervalSinceReferenceDate: 557152051) }, gitHub: .mock ) }

26:42

We now need to convert Analytics to be mockable. We can follow a similar path that we did with our GitHub client. Analytics has a single track method, which executes a side effect. We can extract this to a private function that Analytics is configured with by default. struct Analytics { … var track = track(_:) } private func track(_ event: Analytics.Event) { print("Tracked", event) }

27:36

Let’s now create a mock version. Just as we extended our GitHub struct, we can now extend Analytics with a static mock . extension Analytics { static let mock = Analytics(track: { event in print("Mock track", event" }) }

28:12

With that, we update our mock environment accordingly. extension Environment { static let mock = Environment( analytics: .mock, date: { Date(timeIntervalSinceReferenceDate: 557152051) }, gitHub: .mock ) } And our view controller (and anything else depending on our analytics client), must depend on Current.analytics .

28:55

We’ve now taken an application that hits GitHub, depends on the current date, and hits an analytics service, and fully controlled these dependencies. This means restoring our ability to work on this application without an internet connection, but we can also be sure that we’re not muddying live analytics with testing.

30:04

This is a small example, but the changes are surgical, and we can extract each dependency one by one. What’s the point?

31:10

At the end of this show, we ask “What’s the point?” because we want to bring abstract concepts down to earth, but that’s what this entire episode has been about. Maybe the most abstract thing we talked about was the term “dependency injection,” which can be a scary term. This episode has been all about providing an at-hand solution to that!

31:41

It’s important to address the elephant in the room: we’ve introduced a global singleton (the singleton of all singletons?). What have we wrought? This is the entirety of our dependency injection “framework”: struct Environment { var analytics = Analytics() var date: () -> Date = Date.init var gitHub = GitHub() } var Current = Environment() Compare this with injecting dependencies manually, where objects’ initializers needed to be aware of each dependency but also be sure to pass each dependency deeper as needed by a sub-dependency.

32:44

This is a lot of ceremony and process around something that should be simple. A good way of justifying our dependency injection “framework” of Current is to remember that side effects can be hidden inputs, and by putting them in Current they’re no longer hidden.

33:38

We practice what we preach. We used this system at Kickstarter and we used this system on the code that runs this site. This system works as well for iOS apps as it does for web apps!

34:00

Focusing on “the point” is still important, though, and we can consider focusing on side effects as the point! Everywhere we’ve marked our codebase with Current we can know: this is a side effect. When we typically work in languages where side effects are sprinkled everywhere, this provides a way of highlighting where things happen. Whenever we may sprinkle a side effect into our app, when we stumble on a problem related to it, we can throw it into Environment , add a mock, and we’re good to go.

34:50

There’s a lot more to cover here. Stay tuned! 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 0016-dependency-injection 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 .