0

I have a class (Swift 3) in my app that contains the basic CoreData stack which I moved out of AppDelegate. All of the operations the app needs to perform are in another class I call CoreDataHelper. When the app is launched, AppDelegate creates an instance of CoreDataHelper (let dataManager = CoreDataHelper()) which is the only thing that talks to the stack. Then in launchCalculatorViewController(), dataManager asks the stack for a new LiftEvent:

class AppDelegate: UIResponder, UIApplicationDelegate {
    var window: UIWindow?
    let dataManager = CoreDataHelper()

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

    // check if data needs to be loaded and load it if needed...

      launchCalculatorViewController()    
      return true
    }

    func launchCalculatorViewController() {
      self.window = UIWindow(frame: UIScreen.main.bounds)
      let mainStoryboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
      if let initialViewController: CalculatorViewController = mainStoryboard.instantiateInitialViewController() as? CalculatorViewController {

      // create the liftEvent, the viewModel, and wire it up to the view controller
      let liftEvent = dataManager.createNewLiftEvent() // go get me a new LiftEvent
      let viewModel = calculatorLiftEventViewModelFromLiftEvent(withLiftEvent: liftEvent, dataManager: dataManager)
      initialViewController.viewModel = viewModel
      initialViewController.dataManager = dataManager

      self.window?.rootViewController = initialViewController
      self.window?.makeKeyAndVisible()
    }
}

here's the createNewLiftEvent() method in dataManager:

func createNewLiftEvent() -> LiftEvent {
    let newLiftEvent = LiftEvent(dataManager: self, insertIntoManagedObjectContext: stack.managedObjectContext)

    return newLiftEvent
}

and the initialization of LiftEvent looks like this:

class LiftEvent: NSManagedObject, LiftEventProtocol {

    var dataManager: CoreDataHelper!

    override func awakeFromInsert() {
        super.awakeFromInsert()

        let date = Date()
        self.date = date

        let defaultUnit = UserDefaults.weightUnit()

        if defaultUnit == "kg" {
            weight = Measurement(value: 0.0, unit: .kilograms)
            liftWeights[defaultUnit] = weight
        } else {
            weight = Measurement(value: 0.0, unit: .pounds)
            liftWeights[defaultUnit] = weight
        }

        if let defaultFormula = dataManager.fetchDefaultFormula() { // unexpected nil error thrown here
            self.formula = defaultFormula // this is an NSManagedObject 
        }                                 
    }

    convenience init(dataManager: CoreDataHelper, insertIntoManagedObjectContext context: NSManagedObjectContext!) {
        let entity = NSEntityDescription.entity(forEntityName: "LiftEvent", in: context)!
        self.init(entity: entity, insertInto: context) // exits after this line
        self.dataManager = dataManager // never gets executed so of course it's nil
    }
}

I get the "unexpectedly found nil while unwrapping an Option value" error because dataManager is nil at this point. I try to set it in the convenience initializer but self.dataManager = dataManager is never executed because it exits right after self.init. Of course I can't put it before self.init because the object has to exist before I can set the value.

I'm passing in dataManager in an attempt to ensure that the Formula (also an NSManagedObject) and this new LiftEvent being created are in the same managed object context so I can set the relationship between them in awakeFromInsert(). Before trying this approach it was crashing because the LiftEvent and Formula were it two different managedObjectContexts (even though I did a lot of looking to make sure that dataManager and the stack were only instantiated once). The approach above was inspired by this thread, but I think I might not understand initialization as well as I thought I did.

How do I pass in the managed object context to create a LiftEvent so it's in the same managed object context as Formula so the relationship can be set? Or, is the totally the wrong approach for this?

Community
  • 1
  • 1
Jim
  • 1,260
  • 15
  • 37
  • At the point `let liftEvent = dataManager.createNewLiftEvent()` is called, won't dataManager be nil? What has created it before this line? – Michael May 18 '17 at 06:02
  • It's not nil there because the first thing AppDelegate does is `let dataManager = CoreDataHelper()` and then `let liftEvent = dataManager.createNewLiftEvent()` is called after that. The second chunk of code is in `dataManager` and as you can see it passes itself as an argument. When I step through the convenience initializer, I can see that `self.dataManager = dataManager` is never executed because it exits that method right after `self.init(entity: entity, insertInto: context)`. I'll update the code to make this clearer. – Jim May 18 '17 at 19:58
  • I don't quite see how `dataManager` is the source of your problem. Are you sure `entity` and `context` are valid at the point where you init the `NSManagedObject` superclass? – Phillip Mills May 19 '17 at 20:00
  • @PhillipMills I just checked to be sure and they appear to be valid. With a breakpoint at `super.awakeFromInsert()`, `po entity.name` gives me `Optional - some : "LiftEvent"` and `po managedObjectContext` gives me ``. – Jim May 19 '17 at 22:18

0 Answers0