Search code examples
swiftcore-datansfetchedresultscontrollernsmanagedobjectcontext

Finding nil with optional managedObject, using CoreData and NSFetchedResultsController


I am trying to populate a collectionView with some CoreData. I'm programming in Swift and am using iOS 9. I have most everything hooked up correctly (to my knowledge), but I keep having a crash due to one line of code.

Here is the code from my viewController. The line that errors out with nil is "managedObjectContext: coreDataStack.context":

override func viewDidLoad() {
    super.viewDidLoad()

    //1
    let fetchRequest = NSFetchRequest(entityName: "Family")

    let firstNameSort =
    NSSortDescriptor(key: "firstName", ascending: true)

    fetchRequest.sortDescriptors = [firstNameSort]

    //2
    fetchedResultsController =
        NSFetchedResultsController(fetchRequest: fetchRequest,
            managedObjectContext: coreDataStack.context,
            sectionNameKeyPath: nil,
            cacheName: nil)

    fetchedResultsController.delegate = CollectionViewFetchedResultsControllerDelegate(collectionView: familyCollectionView)

    //3
    do {
        try fetchedResultsController.performFetch()
    } catch let error as NSError {
        print("Error: \(error.localizedDescription)")
    }
}

Below is the "CoreDataStack" object that I reference:

import Foundation
import CoreData

class CoreDataStack {
let modelName = "CoreDataModel"

lazy var context: NSManagedObjectContext = {

    var managedObjectContext = NSManagedObjectContext(concurrencyType: .MainQueueConcurrencyType)

    managedObjectContext.persistentStoreCoordinator = self.psc

    return managedObjectContext
}()

private lazy var psc: NSPersistentStoreCoordinator = {

    let coordinator = NSPersistentStoreCoordinator(
        managedObjectModel: self.managedObjectModel)

    let url = self.applicationDocumentsDirectory
        .URLByAppendingPathComponent(self.modelName)

    do {
        let options =
        [NSMigratePersistentStoresAutomaticallyOption : true]

        try coordinator.addPersistentStoreWithType(
            NSSQLiteStoreType, configuration: nil, URL: url,
            options: options)
    } catch  {
                print("Error adding persistent store.")
    }

    return coordinator
}()

private lazy var managedObjectModel: NSManagedObjectModel = {

    let modelURL = NSBundle.mainBundle()
        .URLForResource(self.modelName,
        withExtension: "momd")!
    return NSManagedObjectModel(contentsOfURL: modelURL)!
}()

private lazy var applicationDocumentsDirectory: NSURL = {
    let urls = NSFileManager.defaultManager().URLsForDirectory(
    .DocumentDirectory, inDomains: .UserDomainMask)
    return urls[urls.count-1]
}()

func saveContext () {
    if context.hasChanges {
    do {
        try context.save()
    } catch let error as NSError {
            print("Error: \(error.localizedDescription)")
            abort()
    }
    }
}

}

Any ideas on why I'm picking up nil when I call that line?


Solution

  • The problem exists because self.coreDataStack was never assigned however it was accessed when the fetchedResultsController was defined.

    To simply solve the problem, create and assign as instance of CoreDataStuck to your view controller's coreDataStack property before it is accessed:

    self.coreDataStack = CoreDataStack()
    

    I would like to stress the importance of managing the NSManagedObjectContext however. I believe it is a more common pattern to create an instance of CoreDataStack and pass a reference to it as you need it. For instance, you can create an instance of it within your Application Delegate and assign the reference to your first view controller with something like this:

    lazy var cds = CoreDataStack()
    
    func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSOBject: AnyObject?]?) -> Bool {
    
    // Obtain a reference to your root view controller, this may have to be different if you use different types of controllers. This would be the View controller where you use the core data stack in your question though.
    let vc = window!.rootViewController as! ViewController
    vc.coreDataStack = self.cds // This will now ensure the property on your view controller is assigned and can be accessed.
    return true
    }
    

    Later on in your app, I would then suggest passing this same reference around. You could do it by accessing the app delegate, personally I think it's cleaner to just pass along a reference through the prepare for segue method. I'll just imagine there's a SecondViewController class that exists in your app and that you're transitioning to it from the ViewController in your question:

    class SecondViewController: UIViewController {
    
        var cds: CoreDataStack!
    
    }
    

    I'll assume there's a segue between ViewController and SecondViewController with the name "next". In the ViewController file, define the prepareForSegue method:

    override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
    
        if segue.identifier == "next" {
    
            let secondVc = segue.destinationViewController as! SecondViewController
            secondVc.cds = self.coreDataStack 
    
        }
    
    }
    

    I tried to stress the idea of keeping a reference to the instance of the core data stack that's first created, I think as you go along you'll find it more beneficial.