Dependency injection is a design pattern to leverage inversion of control to manage dependencies. Rather than creating instances explicitly you ask someone for instances to fulfill a certain task. In iOS a typical dependency is a NSNotificationCenter. Rather than getting the defaultCenter() explicitly you should just ask for a NSNotificationCenter to use, since it’s possible that the system doesn’t want to use the default center at the moment (e.g. in a unit test).

Dependency injection has a lot of advantages and it makes your code cleaner and easier to test. You can read more about it on Wikipedia or Martin Fowler’s great post.

Unfortunately dependency injection is not that common in the iOS world but there are some great frameworks out there such as Objection.

For Swift the situation is a bit more difficult. Since it doesn’t have all the preprocessing macros - which is good because they were hard to debug and could mess up a lot - we cannot implement or use an annotation-based approach like Objection in Swift. But you don’t need fancy frameworks to do dependency injection and with the compact syntax of Swift it’s not that much effort to write it.

First of all lets collect the goals of the dependency injection solution we want to build:

  • ask for dependencies - don’t tell
  • encourage “program to interfaces, not implementations”
  • provide a central definition of dependencies and configurations
  • provide a central place for all singletons
  • make the dependencies to other classes and especially singletons explicit
  • make classes with dependencies easily testable

Lets start with the AppContext which configures and creates all dependencies. This is the object which is asked to provide concrete instances.

class AppContext {
  static var ctx = {
    return AppContext()
  }()
    
  // MARK: singletons
    
  lazy var notificationCenter = {
    return NSNotificationCenter.defaultCenter()
  }()
    
  // MARK: object creation

  var newSampleAPIClient = {
    return SampleAPIClient(baseUrlString: "https://awesomeService.com/api/")
  }
}

The AppContext is a singleton and accessible via AppContext.ctx. Singleton dependencies like NSNotificationCenter are defined as lazy vars and other dependencies are defined as closures returning new instances.

In the AwesomeViewController it’s easy to get the NSNotificationCenter and a new SampleAPIClient from the AppContext:

class AwesomeViewController: UIViewController {
  let notificationCenter = AppContext.ctx.notificationCenter
  let apiClient = AppContext.ctx.newSampleAPIClient()
    
  // ...
}

The setup for an unit test with mocking of the SampleApiClient and providing an alternative NSNotificationCenter (using the defaultCenter in unit tests is a very bad idea because of side effects) could look like this:

class AwesomeViewControllerTest: XCTestCase {

  let testNotificationCenter = NSNotificationCenter()
  let testAPIClient = MockSampleAPIClient()
    
  let vcUnderTest: AwesomeViewController!
    
  override func setup() {
    // use a clean AppContext for every test 
    AppContext.ctx = AppContext()   
    // replace the default notificationCenter
    AppContext.ctx.notificationCenter = testNotificationCenter
    // replace the default creation closure for SampleAPIClients
    AppContext.ctx.newSampleAPIClient = {testAPIClient} 
        
    vcUnderTest = AwesomeViewController()
  }
   
  // unit tests ...
     
  // MARK: mocks
    
  class MockSampleAPIClient: SampleAPIClient {
    // mock methods to avoid actual network communication but check that they are called correctly ...
  }
}

We can go even one step further in terms of safety. At the moment anyone can change the configuration of the AppContext during the normal program runtime. This might be dangerous, cause someone could set another NSNotificationCenter and suddenly posted notifications do not reach all instances anymore! Lets improve this by using let and protocols.

protocol AppContext {
  var notificationCenter: NSNotificationCenter {get}
  var newSampleAPIClient: () -> (SampleAPIClient) {get}
}

class AppContextProvider {
  static var ctx = {
    return AppContext()
  }()
}

class DefaultAppContext: AppContext {
    
  let notificationCenter = {
    return NSNotificationCenter.defaultCenter()
  }()
    
  let newSampleAPIClient = {
    return SampleAPIClient(baseUrlString: "https://awesomeService.com/api/")
  }
}

And for unit tests create a new TestAppContext with the var behaviour that was used before.

class TestAppContext: AppContext {
    
  lazy var notificationCenter = {
    return NSNotificationCenter()
  }()
    
  var newSampleAPIClient = {
    return SampleAPIClient(baseUrlString: "https://testService.com/api/")
  }
}

Have fun with cleaner and better testable Swift code! :-)