1

In Cocoa, Storyboard's first view controller will call viewDidLoad (on the first view controller) before AppDelegate's applicationDidFinishLaunching is called.
Since I am grabbing my NSManagedObjectContext in applicationDidFinishLaunching, I need to wait for applicationDidFinishLaunching before loading my data.

In other words, in viewDidLoad, I don't yet have my NSManagedObjectContext.


What I'm doing now:

I'm adding an applicationDidFinishLaunching observer in my viewDidLoad, and load the data when that is triggered.

So (in order):
1. ViewController is adding an applicationDidFinishLaunching observer.
2. AppDelegates runs its applicationDidFinishLaunching and triggering the observer.
3. I can load the data from my ViewController.


I realized I'm relaying on viewDidLoad to be called before applicationDidFinishLaunching. If that order is changed, the observer will be added after applicationDidFinishLaunching and data will not load.

Would it be 'safer' to let my 'CoreDataManager' get the NSManagedObjectContext from AppDelegate directly in its init?

bauerMusic
  • 5,470
  • 5
  • 38
  • 53
  • The real question here is, why is `viewDidLoad` being called before `applicationDidFinishLaunching`? That's not normal. Something weird is happening, and Core Data is the symptom, not the cause. – Tom Harrington Apr 16 '18 at 19:23
  • @TomHarrington I'm afraid it is, in the land of Cocoa: https://stackoverflow.com/questions/36681587/os-x-storyboard-calls-viewdidload-before-applicationdidfinishlaunching – bauerMusic Apr 16 '18 at 20:08
  • Sorry, I was thinking iOS. Nevermind. – Tom Harrington Apr 16 '18 at 20:12
  • @TomHarrington You hit the big point. This is really counter intuitive where in Apple's examples, they instruct the `NSManagedObjectContext` pointer passing right in the `applicationDidFinishLaunching`. – bauerMusic Apr 17 '18 at 02:51

2 Answers2

2

May I suggest a revised design instead, remove any use of core data from the AppDelegate class and move any initialisation into your root view controller instead and then use dependency injection to pass your managed object context to other view controllers (or have a separate core data manager class implemented as a singleton). This will free you from issues like this.

Joakim Danielson
  • 43,251
  • 5
  • 22
  • 52
  • Took me some time to realize this is really the best way. I basically just added some explanation and code, but yea, the CoreData code can be easily moved, it's one independent function. – bauerMusic Apr 17 '18 at 16:28
0

The answer provided by Joakim Danielson here is what I ended up doing (credit where credit is due).

--EDIT

Ended up doing exactly what Joakim Danielson suggested. The given code to get the context, may be in the AppDelegate by default, but it's completely independent. It can be moves anywhere really.
I basically moved the following func into my CoreDataManager class and I'm calling it in the init():

lazy var persistentContainer: NSPersistentContainer = {

    let container = NSPersistentContainer(name: "AppName")
    container.loadPersistentStores(completionHandler: { (storeDescription, error) in
        if let error = error {
            // Replace this implementation with code to handle the error appropriately.
            fatalError("Unresolved error \(error)")
        }
    })
    return container
}()

Also, not to forget that the saveContext() in AppDelegate needs to be updated (by default it points to its own getter).


(Previous answer:)

I've changed (somewhat) the signature lazy var persistentContainer: NSPersistentContainer provided in the AppDelegate (by checking CoreData when creating a project) to: static var persistentContainer: NSPersistentContainer.

static var coreDataContext: NSManagedObjectContext = {

    let container = NSPersistentContainer(name: "AppName")
    container.loadPersistentStores(completionHandler: { (storeDescription, error) in
        if let error = error {
            // Replace this implementation with code to handle the error appropriately.
            fatalError("Unresolved error \(error)")
        }
    })
    return container.viewContext
}()

My CoreDataManager (which is a singleton), can now use this init:

class CoreDataManager {

static let shared = CoreDataManager()

weak var context: NSManagedObjectContext!

init() {
    self.context = AppDelegate.coreDataContext
}

Using this pattern (in AppDelegate):

static var coreDataContext: NSManagedObjectContext = {...}()

Ensures that the block will be executed only once.

Now, it is safe to use the NSManagedObjectContext at any stage.

bauerMusic
  • 5,470
  • 5
  • 38
  • 53