Dependency Injection in iOS with SwiftUI

 

A Beginner-Friendly Guide to Building Modular, Testable Apps

Dependency Injection (DI) is a powerful design pattern that helps you write more modular, testable, and maintainable code. In SwiftUI, while the framework encourages composition and simplicity, adopting DI can elevate your architecture — especially as your app grows.

In this post, you’ll learn:

  • What Dependency Injection is
  • Why it’s useful in SwiftUI apps
  • Different DI techniques in Swift
  • Real-world SwiftUI examples with DI
  • Best practices and when to use DI

🧠 What is Dependency Injection?

Dependency Injection means providing an object with its dependencies from the outside instead of creating them internally.

Think of it this way: instead of a class making its own tools, it is handed the tools it needs.

Without DI:

class UserViewModel {
private var userService = UserService()
}

With DI:

class UserViewModel {
private var userService: UserService

init(userService: UserService) {
self.userService = userService
}
}

💡 Why Use Dependency Injection in SwiftUI?

SwiftUI heavily uses composition and state-driven UI, which fits well with DI. Here’s why it’s beneficial:

  • ✅ Promotes loose coupling
  • ✅ Improves unit testability
  • ✅ Allows easy mocking during previews
  • ✅ Encourages reusability and scalability

🧰 Types of Dependency Injection

There are 3 main techniques in Swift:

1. Initializer Injection

Recommended for value types and view models.

struct ContentView: View {
let viewModel: UserViewModel

var body: some View {
Text(viewModel.user.name)
}
}

2. Property Injection

Useful when you can’t inject through init (e.g., SwiftUI Views).

class HomeViewModel: ObservableObject {
var networkManager: NetworkManager!
}

3. Environment Injection (SwiftUI’s Way)

Use @EnvironmentObject or custom @Environment keys.

struct ContentView: View {
@EnvironmentObject var viewModel: UserViewModel

var body: some View {
Text(viewModel.user.name)
}
}

🔁 Real-World Example: Using DI in SwiftUI

Step 1: Define the Protocol

protocol UserServiceProtocol {
func fetchUser() -> User
}

Step 2: Create a Mock & Real Service

class MockUserService: UserServiceProtocol {
func fetchUser() -> User {
return User(name: "Mock User")
}
}

class RealUserService: UserServiceProtocol {
func fetchUser() -> User {
// API Call here
return User(name: "Live User")
}
}

Step 3: ViewModel with DI

class UserViewModel: ObservableObject {
private let userService: UserServiceProtocol
@Published var user: User

init(userService: UserServiceProtocol) {
self.userService = userService
self.user = userService.fetchUser()
}
}

Step 4: Inject into View

struct ContentView: View {
@StateObject var viewModel: UserViewModel

var body: some View {
Text("Hello, \(viewModel.user.name)")
}
}
// Inject via preview or app entry point
ContentView(viewModel: UserViewModel(userService: RealUserService()))

🧪 Injecting Mock Data in SwiftUI Preview

struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView(viewModel: UserViewModel(userService: MockUserService()))
}
}

🧼 Best Practices

  • Use protocols to decouple services from implementations
  • Inject mocks for previews and unit tests
  • Prefer initializer injection when possible
  • Use Environment for global services (e.g., themes, user session)
  • Avoid overengineering — start simple, scale when needed

🧩 Swift Package Managers and DI Frameworks

You can also use frameworks like:

Example with Resolver:

extension Resolver: ResolverRegistering {
public static func registerAllServices() {
register { RealUserService() as UserServiceProtocol }
register { UserViewModel(userService: resolve()) }
}
}

✅ Conclusion

Dependency Injection isn’t just for massive apps or backend code. Even in SwiftUI, embracing DI leads to better separation of concerns, easier testing, and more flexible architecture.

Start small: use protocol-based injection for your services and adopt previews with mocks.

Have you used Dependency Injection in your SwiftUI apps? Let me know your experience in the comments.

Comments

Popular posts from this blog

Using Core ML with SwiftUI: Build an AI-Powered App

Infinite Scrolling in SwiftUI with Real-Time Pagination