DEV Community

Cover image for How to Build Maintainable iOS Apps with Dependency Injection
Manu Rodríguez
Manu Rodríguez

Posted on

How to Build Maintainable iOS Apps with Dependency Injection

Hey there fellow developers! Are you tired of your iOS apps becoming an unmaintainable mess of spaghetti code?
Spaguetti!
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.

protocol UserRepository {
    func getUsers(completion: @escaping ([User]) -> Void)
}
Enter fullscreen mode Exit fullscreen mode

Next, we create an implementation of the repository that uses an instance of UserService to retrieve the data:

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)
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

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:

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
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

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:

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")
    }
}
Enter fullscreen mode Exit fullscreen mode

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.

Top comments (0)