I'm not going to lie. I don't like singletons. Singletons are fine if they're used correctly, but I don't like singletons for convenience. They almost always lead to problems down the line.

This means that the Core Data manager isn't going to be a singleton. We're going to create an instance in the application delegate and inject it into the root view controller. Dependency injection is surprisingly easy if you break it down to its bare essentials.

We first open ViewController.swift and create a property for the Core Data manager. The property is an optional because we set it after the view controller is initialized. That's a drawback I'm happy to accept.

ViewController.swift

import UIKit

class ViewController: UIViewController {

    // MARK: - Properties

    var coreDataManager: CoreDataManager?

    ...

}

Next, we open AppDelegate.swift and declare a private constant property, coreDataManager, of type CoreDataManager. We instantiate a CoreDataManager instance and assign it to the property.

AppDelegate.swift

import UIKit

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

    // MARK: - Properties

    var window: UIWindow?

    // MARK: -

    private let coreDataManager = CoreDataManager(modelName: "Notes")

    ...

}

The magic happens in the application(_:didFinishLaunchingWithOptions:) method. We load the main storyboard and instantiate its initial view controller. We expect the initial view controller to be an instance of the ViewController class. If it isn't, we throw a fatal error.

AppDelegate.swift

// Load Storyboard
let storyboard = UIStoryboard(name: "Main", bundle: Bundle.main)

// Instantiate Initial View Controller
guard let initialViewController = storyboard.instantiateInitialViewController() as? ViewController else {
    fatalError("Unable to Configure Initial View Controller")
}

We configure the initial view controller by setting its coreDataManager property. In other words, we inject the Core Data manager into the view controller through property injection. That's all dependency injection is, setting an object's instance variables.

AppDelegate.swift

// Configure Initial View Controller
initialViewController.coreDataManager = coreDataManager

Last but not least, we set the rootViewController property of the window property of the application delegate.

AppDelegate.swift

// Configure Window
window?.rootViewController = initialViewController

This is what the implementation of application(_:didFinishLaunchingWithOptions:) looks like when you're finished.

AppDelegate.swift

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
    // Load Storyboard
    let storyboard = UIStoryboard(name: "Main", bundle: Bundle.main)

    // Instantiate Initial View Controller
    guard let initialViewController = storyboard.instantiateInitialViewController() as? ViewController else {
        fatalError("Unable to Configure Initial View Controller")
    }

    // Configure Initial View Controller
    initialViewController.coreDataManager = coreDataManager

    // Configure Window
    window?.rootViewController = initialViewController

    return true
}

Let's give it a try by adding a print statement to the viewDidLoad() method of the ViewController class. Build and run the application and inspect the output in the console.

ViewController.swift

override func viewDidLoad() {
    super.viewDidLoad()

    print(coreDataManager?.managedObjectContext ?? "No Managed Object Context")
}

This example illustrates how easy it is to use dependency injection to pass objects around without relying on singletons. Truth be told, there's no need to instantiate the Core Data manager in the application delegate, but I hope it shows that singletons aren't the only solution and it certainly shouldn't be your default choice.

Before we move on, I'd like to move the instantiation of the Core Data manager to the view controller. That makes more sense. The application delegate shouldn't be bothered with anything related to Core Data.

ViewController.swift

import UIKit

class ViewController: UIViewController {

    // MARK: - Properties

    private var coreDataManager = CoreDataManager(modelName: "Notes")

    // MARK: - View Life Cycle

    override func viewDidLoad() {
        super.viewDidLoad()
    }

    // MARK: - Navigation

    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {

    }

}

AppDelegate.swift

import UIKit

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

    // MARK: - Properties

    var window: UIWindow?

    // MARK: - Application Life Cycle

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
        return true
    }

}

We also have the advantage that the coreDataManager property is no longer an optional. That's a nice benefit. In the next episodes, we take a close look at the data model.