Search code examples
swiftcore-dataoption-typensfetchrequest

How to prevent fatal error: unexpectedly found nil wile unwrapping an Optional value


So I'm new to core data, starting doing some tutorials and got a very basic setup where a 10 scores are saved from the AppDelegate and that works fine (am able to fetch and print from the console).

Then when I want to use a fetchrequest in another VC to retrieve the data which has been saved successfully, I get the following error: fatal error: unexpectedly found nil while unwrapping an Optional value. Here's the code to retrieve the data:

import Foundation
import CoreData
import UIKit


class testVC: UIViewController {
var managedObjectContext: NSManagedObjectContext!
var scores = [Scores]()

@IBOutlet weak var retrieveDataLabel: UIButton!
@IBOutlet weak var saveLabel: UIButton!
  override func viewDidLoad() {
    super.viewDidLoad()

     }


@IBAction func retrieveData(sender: AnyObject) {
    let fetchRequest = NSFetchRequest(entityName: "Scores")
    do {
        if let results = try managedObjectContext.executeFetchRequest(fetchRequest) as? [Scores]{
            scores = results
            for result in results {
                if let gameScore = result.valueForKey("gameScore") as? Int{
                    print("Your score is \(gameScore)")
                }
            }
        }
    }catch{
        print("Error fetching data")
    }}

override func didReceiveMemoryWarning() {
    super.didReceiveMemoryWarning()
    // Dispose of any resources that can be recreated.
}

}

Since I'm using if let .. shouldn't I be getting "Error fetching data"? instead of getting the fatal error? How come I can retrieve the data when loading the app but not when I try to retrieve the data in a different VC using the same code?

I've looked at other questions relating to fatal error found nil while unwrapping an optional value: What does "fatal error: unexpectedly found nil while unwrapping an Optional value" mean?

fatal error: unexpectedly found nil while unwrapping an Optional value and it seems that I'm basically trying to retrieve something which isn't there, but the data is saved indeed and I can retrieve it in the console when starting the app. Using the same fetch request I'd expect to get identical results when I load the app and retrieve the data but that's not the case.

What am I missing here? It feels like it should be very basic but for some reason just cannot get it to work after trying for 2 days.

app delegate code to save and retrieve data:

 func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
    // Override point for customization after application launch.
    //addTestData()
    let fetchRequest = NSFetchRequest(entityName: "Scores")
    do {
        if let results = try managedObjectContext.executeFetchRequest(fetchRequest) as? [NSManagedObject]{
            for result in results {
                if let gameScore = result.valueForKey("gameScore") as? Int{
                    print("Your score is  \(gameScore)")
                }
            }
        }
    }catch{
        print("Error fetching data")
    }

    return true
}
func addTestData(){

    guard let entity = NSEntityDescription.entityForName("Scores", inManagedObjectContext: managedObjectContext) else{
        fatalError("Could not find entity description")
    }
    for i in 1...10{
        let score = Scores(entity: entity, insertIntoManagedObjectContext: managedObjectContext)
        score.gameScore = i
    }
    do {
        try managedObjectContext.save()
    } catch {
        // Replace this implementation with code to handle the error appropriately.
        // abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
        let nserror = error as NSError
        NSLog("Unresolved error \(nserror), \(nserror.userInfo)")
        abort()
    }

}

Any help would be greatly appreciated!


Solution

  • The issue is the nil NSManagedObjectContext (declared but no value)

    By definition executeFetchRequest returns a non-optional array on success and since you are supposed to know from the Core Data model that the entity Scores returns always Scores objects you can safely write

    do {
        scores = try managedObjectContext.executeFetchRequest(fetchRequest) as! [Scores]
    

    And obviously using a NSManagedObject subclass use the property directly to avoid the type casting. You need the optional binding only if the property gameScore is declared as optional.

        for result in results {
            if let gameScore = result.gameScore {
                print("Your score is \(gameScore)")
            }
    

    However for a Int value like a score a non-optional is more reasonable, than you can reduce the code to

        for result in results {
           print("Your score is \(result.gameScore)")
        }