How to Build Maintainable iOS Apps with Dependency Injection
Tags: swift, ios, cleancode, testing
Published: 2023-04-29 18:43:46

Hey there fellow developers! Are you tired of your iOS apps becoming an unmaintainable mess of spaghetti code? 
![Spaguetti!](https://media.giphy.com/media/11uoNyauChZR16/giphy.gif)
Fear not, for there is a solution: dependency injection. In this article, we'll show you how to use dependency injection to create clean, testable code that's easy to maintain.

If you're new to dependency injection or just want to improve your iOS development skills, then this article is for you. Let's get started!

### **Take Control of Your Code: 5 Key Benefits of Dependency Injection in iOS Programming**

Before we dive into the nitty-gritty, let's talk about the benefits of using dependency injection in your iOS apps:

- **Increased modularity:** Dependency injection allows objects to be created outside of a class and passed as parameters, making code more modular.
- **Reduced coupling:** Injected objects are not coupled to a particular class, making code more flexible and easier to maintain.
- **Increased code reuse:** Injected objects can be reused in multiple classes and applications, reducing code duplication and improving efficiency.
- **Easier testing:** Dependency injection makes it easier to perform unit testing on code, as injected objects can be replaced with test objects.
- **Improved performance:** By avoiding the creation of unnecessary objects, dependency injection can improve the performance of the application.

### Dependency Injection in action!

Let's say you want to retrieve a list of users from a repository. Normally, you might create an instance of the repository directly in your view or presenter class. But with dependency injection, we can create an interface for the repository and pass in an implementation of it at runtime.

```swift
protocol UserRepository {
    func getUsers(completion: @escaping ([User]) -> Void)
}
```
Next, we create an implementation of the repository that uses an instance of **`UserService`** to retrieve the data:

```swift
class UserRepositoryImp: UserRepository {
    private let userService: UserService
    
    init(userService: UserService) {
        self.userService = userService
    }
    
    func getUsers(completion: @escaping ([User]) -> Void) {
        userService.fetchUsers { users in
            completion(users)
        }
    }
}
```
In this example, **`UserRepositoryImp`** uses an instance of **`UserService`** to retrieve the data. By using dependency injection, we can pass any implementation of **`UserService`** to **`UserRepositoryImp`** and use it to retrieve the data.

Now, to use **`UserRepositoryImp`** in our application, we can inject it into our view or presenter class, for example:

```swift
class UserListPresenter {
    private let userRepository: UserRepository
    private var users: [User] = []
    
    init(userRepository: UserRepository) {
        self.userRepository = userRepository
    }
    
    func getUsers() {
        userRepository.getUsers { [weak self] users in
            self?.users = users
            // update view with users
        }
    }
}
```
In this example, **`UserListPresenter`** uses **`UserRepository`** to retrieve the data. By using dependency injection, we can pass any implementation of **`UserRepository`** to **`UserListPresenter`** and use it to retrieve the data.

### **And… how to do tests with dependency injection?**

Finally, to perform unit testing on our code, we can create a mocked implementation of **`UserRepository`** that returns test data and pass it to our test class:

```swift
class MockUserRepository: UserRepository {
    func getUsers(completion: @escaping ([User]) -> Void) {
        let users = [User(name: "User 1"), User(name: "User 2"), User(name: "User 3")]
        completion(users)
    }
}

class UserListPresenterTests: XCTestCase {
    func testGetUsers() {
        let mockRepository = MockUserRepository()
        let presenter = UserListPresenter(userRepository: mockRepository)
        
        presenter.getUsers()
        
        XCTAssertEqual(presenter.users.count, 3)
        XCTAssertEqual(presenter.users[0].name, "User 1")
        XCTAssertEqual(presenter.users[1].name, "User 2")
        XCTAssertEqual(presenter.users[2].name, "User 3")
    }
}
```
In this example, we create a mocked implementation of **`UserRepository`** called **`MockUserRepository`**, which returns a list of test users. We then create an instance of **`UserListPresenter`** with **`MockUserRepository`** injected and call the **`getUsers()`** method to retrieve the data. Finally, we check that the data was retrieved correctly.

I hope this example helps you understand how to use dependency injection to retrieve data from a repository and implement an interface to aid in testing.