20.7 C
Canberra
Friday, October 24, 2025

What’s dependency injection in Swift? – Donny Wals


Code has dependencies. It’s one thing that I think about universally true in a method or one other. Generally these dependencies are third celebration dependencies whereas different occasions you’ll have objects that rely upon different objects or performance to perform. Even if you write a perform that ought to be known as with a easy enter like a quantity, that’s a dependency.

We regularly don’t actually think about the small issues the be dependencies and this publish is not going to give attention to that in any respect. In an earlier publish, I’ve written about utilizing closures as dependencies, often known as protocol witnesses.

On this publish I’d wish to give attention to explaining dependency injection for Swift. You’ll be taught what dependency injection is, what sorts of dependency injection now we have, and also you’ll be taught a bit in regards to the professionals and cons of the totally different approaches.

In the event you favor studying via video, have a look right here:

Understanding the fundamentals of dependency injection

Dependency Injection (DI) is a design sample that permits you to decouple parts in your codebase by injecting dependencies from the surface, somewhat than hardcoding them inside lessons or structs.

For instance, you may need a view mannequin that wants an object to load person information from some information supply. This could possibly be the filesystem, the networking or another place the place information is saved.

Offering this information supply object to your view mannequin is dependency injection. There are a number of methods wherein we will inject, and there are alternative ways to summary these dependencies.

It’s pretty widespread for an object to not rely upon a concrete implementation however to rely upon a protocol as an alternative:

protocol DataProviding {
  func retrieveUserData() async throws -> UserData
}

class LocalDataProvider: DataProviding {
  func retrieveUserData() async throws -> UserData {
    // learn and return UserData
  }
}

class UserProfileViewModel {
  let dataProvider: DataProviding

  // that is dependency injection
  init(dataProvider: DataProviding) {
      self.dataProvider = dataProvider
  }
}

This code in all probability is one thing you’ve written sooner or later. And also you is perhaps shocked to seek out out that merely passing an occasion of an object that conforms to DataProviding is taken into account dependency injection. It’s simply considered one of a number of approaches you may take however in its easiest type, dependency injection is definitely comparatively easy.

Utilizing dependency injection will make your code extra modular, extra reusable, extra testable, and simply overal simpler to work with. You may make it possible for each object you outline in your code is liable for a single factor which implies that reasoning about components of your codebase turns into so much easier than when you’ve got a lot of advanced and duplicated logic that’s scattered far and wide.

Let’s take a better have a look at initializer injection which is the type of dependency injection that’s used within the code above.

Initializer injection

Initializer injection is a type of dependency injection the place you explicitly go an object’s dependencies to its initializer. Within the instance you noticed earlier, I used initializer injection to permit my UserProfileViewModel to obtain an occasion of an object that conforms to DataProviding as a dependency.

Passing dependencies round like that is almost certainly the only type of passing dependencies round. It doesn’t require any setup, there’s no third celebration options wanted, and it’s all very express. For each object you’re in a position to see precisely what that object will rely upon.

Extra importantly, it’s additionally a really protected method of injecting dependencies; you may’t create an occasion of UserViewModel with out creating and offering your information supplier as properly.

A draw back of this method of dependency injection is that an object may need dependencies that it doesn’t really want. That is very true within the view layer of your app.

Contemplate the instance under:

struct MyApp: App {
  let dataProvider = LocalDataProvider()

  var physique: some Scene {
    WindowGroup {
      MainScreen()
    }
  }
}

struct MainScreen: View {
  let dataProvider: DataProviding
  var physique: some View {
    NavigationStack {
      // ... some views

      UserProfileView(viewModel: UserProfileViewModel(dataProvider: dataProvider))
    }
  }
}

On this instance, now we have an app that has a few views and considered one of our views wants a ProfileDataViewModel. This view mannequin will be created by the view that sits earlier than it (the MainView) however that does imply that the MainView should have the dependencies which might be wanted with a view to create the ProfileDataViewModel. The result’s that we’re creating views which have dependencies that they don’t technically want however we’re required to offer them as a result of some view deeper within the view hierarchy does want that dependency.

In bigger apps this may imply that you just’re passing dependencies throughout a number of layers earlier than they attain the view the place they’re truly wanted.

There are a number of approaches to fixing this. We might, for instance, go round an object in our app that is ready to produce view fashions and different dependencies. This object would rely upon all of our “core” objects and is able to producing objects that want these “core” objects.

An object that’s in a position to do that is known as a manufacturing unit.

For instance, right here’s what a view mannequin manufacturing unit might appear like:

struct ViewModelFactory {
  non-public let dataProvider: DataProviding

  func makeUserProfileViewModel() -> UserProfileViewModel {
    return UserProfileViewModel(dataProvider: dataProvider)
  }

  // ...
}

As a substitute of passing particular person dependencies round all through our app, we might now go our view mannequin manufacturing unit round as a way of fabricating dependencies for our views with out making our views rely upon objects they undoubtedly don’t want.

We’re nonetheless passing a manufacturing unit round far and wide which you’ll or might not like.

In its place method, we will work round this with a number of instruments just like the SwiftUI Surroundings or a device like Resolver. Whereas these two instruments are very totally different (and the main points are out of scope for this publish), they’re each a sort of service locator.

So let’s go forward and check out how service locators are used subsequent.

Service locators

The service locator sample is a design sample that can be utilized for dependency injection. The best way a service locator works is that just about like a dictionary that incorporates all of our dependencies.

Working with a service locator sometimes is a two-step course of:

  1. Register your dependency on the locator
  2. Extract your dependency from the locator

In SwiftUI, this can often imply that you just first register your dependency within the setting after which take it out in a view. For instance, you may have a look at the code under and see precisely how that is accomplished.

extension EnvironmentValues {
  @Entry var dataProvider = LocalDataProvider()
}

struct MyApp: App {
  var physique: some Scene {
    WindowGroup {
      MainScreen()
      .setting(.dataProvider, LocalDataProvider())
    }
  }
}

struct MainScreen: View {
  @Surroundings(.dataProvider) var dataProvider

  var physique: some View {
    NavigationStack {
      // ... some views

      UserProfileView(viewModel: UserProfileViewModel(dataProvider: dataProvider))
    }
  }
}

On this code pattern, I register my view mannequin and an information supplier object on the setting in my app struct. Doing this permits me to retrieve this object from the setting wherever I would like it, so I haven’t got to go it from the app struct via probably a number of layers of views. This instance is simplified so the beneifts aren’t big. In an actual app, you’d have extra view layers, and also you’d go dependencies round much more.

With the method above, I can put objects within the setting, construct my view hierarchy after which extract no matter I would like on the stage the place I would like it. This significantly simplifies the quantity of code that I’ve to jot down to get a dependency to the place it must be and I will not have any views which have dependencies that they do not technically want (like I do with initializer injection).

The draw back is that this method does not likely give me any compile-time security.

What I imply by that’s that if I neglect to register considered one of my dependencies within the setting, I cannot find out about this till I attempt to extract that dependency at runtime. It is a sample that may exist for any sort of service load configuration use, whether or not it is a SwiftUI setting or a third-party library like Resolver.

One other draw back is that my dependencies are actually much more implicit. Because of this although a view is determined by a sure object and I can see that within the record of properties, I can create that object with out placing something in its setting and due to this fact getting crashes when I attempt to seize dependencies from the setting. That is high quality in smaller apps since you’re extra prone to hit all of the required patterns whereas testing, however in bigger apps, this may be considerably problematic. Once more, we’re missing any sort of compile-time security, and that is one thing that I personally miss so much. I like my compiler to assist me write protected code.

That mentioned, there’s a time and place for service locators, particularly for issues that both have a very good default worth or which might be non-obligatory or that we inject into the app root and mainly our complete app is determined by it. So if we might neglect, we would see crashes as quickly as we launch our app.

The truth that the setting or a dependency locator is much more implicit additionally implies that we’re by no means fairly certain precisely the place we inject issues within the setting. If the one place we inject from is the summary or the basis of our software, it is fairly manageable to see what we do and do not inject. If we additionally make new objects and inject them in the midst of our view hierarchy, it turns into so much trickier to cause about precisely the place a dependency is created and injected. And extra importantly, it additionally would not actually make it apparent if at any level we overwrite a dependency or if we’re injecting a recent one.

That is one thing to remember in case you select to make heavy use of a service locator just like the SwiftUI setting.

In Abstract

In brief, dependency injection is an advanced time period for a comparatively easy idea.

We wish to get dependencies into our objects, and we’d like some mechanism to do that. iOS traditionally would not do a variety of third-party frameworks or libraries for dependency injection, so mostly you will both use initializer injection or the SwiftUI setting.

There are third-party libraries that do dependency injection in Swift, however you almost certainly don’t want them.

Whether or not you employ initializer injection or the service locator sample, it is considerably of a mixture between a desire and a trade-off between compile-time security and comfort.

I did not cowl issues like protocol witnesses on this publish as a result of that could be a subject that makes use of initializer injection sometimes, and it is only a totally different sort of object that you just inject. If you wish to be taught extra about protocol witnesses, I do advocate that you just check out my weblog publish the place I discuss utilizing closures as dependencies.

I hope you loved this publish. I hope it taught you numerous about dependency injection. And don’t hesitate to succeed in out to me if in case you have any questions or feedback on this publish.

Related Articles

LEAVE A REPLY

Please enter your comment!
Please enter your name here

[td_block_social_counter facebook="tagdiv" twitter="tagdivofficial" youtube="tagdiv" style="style8 td-social-boxed td-social-font-icons" tdc_css="eyJhbGwiOnsibWFyZ2luLWJvdHRvbSI6IjM4IiwiZGlzcGxheSI6IiJ9LCJwb3J0cmFpdCI6eyJtYXJnaW4tYm90dG9tIjoiMzAiLCJkaXNwbGF5IjoiIn0sInBvcnRyYWl0X21heF93aWR0aCI6MTAxOCwicG9ydHJhaXRfbWluX3dpZHRoIjo3Njh9" custom_title="Stay Connected" block_template_id="td_block_template_8" f_header_font_family="712" f_header_font_transform="uppercase" f_header_font_weight="500" f_header_font_size="17" border_color="#dd3333"]
- Advertisement -spot_img

Latest Articles