iOS 8 Core Data stack - fatal error: found nil while unwrapping an Optional value

iOS 8 Core Data stack - fatal error: found nil while unwrapping an Optional value

我对 iOS 开发比较陌生,因此决定实现我自己的 Core Data 堆栈,替换 Apple 的默认堆栈。

我不得不对我的代码进行更改(很明显)并且已经能够弄清楚,但是在这种情况下我不能。这是我的代码:

import UIKit
import CoreData

class AddTableViewController: UITableViewController, UIImagePickerControllerDelegate, UINavigationControllerDelegate {

    @IBOutlet weak var nameTextField:UITextField!
    @IBOutlet weak var locationTextField:UITextField!
    @IBOutlet weak var imageView:UIImageView!
    @IBOutlet weak var notesView:UITextView!

    var coreDataStack = (UIApplication.sharedApplication().delegate as AppDelegate).coreDataStack

    var backpackerSpot:BackpackerSpot!
    var managedContext: NSManagedObjectContext!

    override func viewDidLoad() {
        super.viewDidLoad()

    }

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

    // TODO Give user the choice of the Photo Library or the Camera
    override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
        if indexPath.row == 0 {
            if UIImagePickerController.isSourceTypeAvailable(.Camera) {
                let imagePicker = UIImagePickerController()
                imagePicker.allowsEditing = false
                imagePicker.delegate = self
                imagePicker.sourceType = .Camera

                self.presentViewController(imagePicker, animated: true, completion: nil)
            }
        }

        tableView.deselectRowAtIndexPath(indexPath, animated: true)
    }

    // FIXME image is being displayed in landscape if it is taken in portrait mode by default
    func imagePickerController(picker: UIImagePickerController!, didFinishPickingImage image: UIImage!, editingInfo: [NSObject : AnyObject]!) {
        imageView.image = image
        imageView.contentMode = UIViewContentMode.ScaleAspectFill
        imageView.clipsToBounds = true

        dismissViewControllerAnimated(true, completion: nil)
    }

    @IBAction func save() {

        //validation
        var errorField = ""

        // TODO have placeholder text in the NOTES field match up with the placholder text in the NAME and LOCATION fields.
        if nameTextField.text == "" {
            errorField = "name"
        } else if locationTextField.text == "" {
            errorField = "location"
        } else if notesView.text == "" {
            errorField = "notes"
        }

        if errorField != "" {

            let alertController = UIAlertController(title: "Error", message: "You must fill in \(errorField).", preferredStyle: .Alert)
            let doneAction = UIAlertAction(title: "OK", style: .Default, handler: nil)
            alertController.addAction(doneAction)

            self.presentViewController(alertController, animated: true, completion: nil)

            return
        }

        // If all fields are correctly filled in, extract the field value
        // Create Restaurant Object and save to data store
//        if let managedObjectContext = (UIApplication.sharedApplication().delegate as AppDelegate).coreDataStack.context {

            let entityBackpackerSpot = NSEntityDescription.entityForName("BackpackerSpot", inManagedObjectContext: coreDataStack.context)

            backpackerSpot?.spotName = nameTextField.text
            backpackerSpot?.spotLocation = locationTextField.text
            backpackerSpot?.spotImage = UIImagePNGRepresentation(imageView.image)
            backpackerSpot?.spotNote = notesView.text

            var error: NSError?
            if !managedContext.save(&error) {
                println("insert error: \(error!.localizedDescription)")
                return
            }
        // Execute the unwind segue and go back to the home screen
        performSegueWithIdentifier("unwindToHomeScreen", sender: self)
    }

}

应用程序启动正常,但是当我单击附加到保存功能的 UIButton 时,它崩溃了,并且出现以下错误:

fatal error: unexpectedly found nil while unwrapping an Optional value
(lldb)

调试器突出显示的行是:

  if !managedContext.save(&error) {

诚然,我不得不花更多时间在 Swift 中使用 Optionals,因为它们似乎经常是我的麻烦源,尽管通常我都能解决。如果有人能指出我正确的方向,那就太好了。谢谢。

编辑:这是我的核心数据堆栈:

import Foundation
import CoreData

class CoreDataStack {

    let model: NSManagedObjectModel
    let storeCoordinator: NSPersistentStoreCoordinator
    let context: NSManagedObjectContext
    let store: NSPersistentStore?

    let dbName = "BackpackerSpots"
    // these options do the migration for us
    let options = [NSInferMappingModelAutomaticallyOption:true,
        NSMigratePersistentStoresAutomaticallyOption: true]

    init() {

        //1 loading model from the file
        let bundle = NSBundle.mainBundle()
        let modelURL = bundle.URLForResource(dbName, withExtension: "momd")
        model = NSManagedObjectModel(contentsOfURL: modelURL!)!

        //2 store coordinator created using that model
        storeCoordinator = NSPersistentStoreCoordinator(managedObjectModel: model)

        //3 context
        context = NSManagedObjectContext()
        context.persistentStoreCoordinator = storeCoordinator

        //4. store
        let documentsURL = MyDocumentDirectory()
        let storeURL = documentsURL.URLByAppendingPathComponent(dbName)

        var error:NSError?

        store = storeCoordinator.addPersistentStoreWithType(NSSQLiteStoreType,
            configuration: nil,
            URL: storeURL,
            options: nil,
            error: &error)

        if store == nil {
            println("error adding persistent store: \(error)")
            abort()
        }
    }

    func saveContext() {
        var error: NSError?

        if context.hasChanges && !context.save(&error) {
            println("Could not save. \(error), \(error?.description)")
        }
    }

}

编辑:这是我的根视图控制器:

import UIKit
import CoreData

class BackpackerSpotTableViewController: UITableViewController, NSFetchedResultsControllerDelegate {

    var coreDataStack = (UIApplication.sharedApplication().delegate as AppDelegate).coreDataStack

//    var backpackerSpot:BackpackerSpot!
    var managedContext: NSManagedObjectContext!

    var backpackerSpots:[BackpackerSpot] = []

    var fetchResultController:NSFetchedResultsController!

    // Let's add some animations!
    override func tableView(tableView: UITableView, willDisplayCell cell: UITableViewCell, forRowAtIndexPath indexPath: NSIndexPath) {
        // initial cell state
        cell.alpha = 0

        // state after animations
        UIView.animateWithDuration(1.0, animations: { cell.alpha = 1})
    }

    override func viewDidLoad() {
        super.viewDidLoad()

        var fetchRequest = NSFetchRequest(entityName: "BackpackerSpot")
        let sortDescriptor = NSSortDescriptor(key: "spotName", ascending: true)
        fetchRequest.sortDescriptors = [sortDescriptor]

//        if let managedObjectContext = (UIApplication.sharedApplication().delegate as AppDelegate).coreDataStack {

        let entityBackpackerSpot = NSEntityDescription.entityForName("BackpackerSpot", inManagedObjectContext: coreDataStack.context)

            fetchResultController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: coreDataStack.context, sectionNameKeyPath: nil, cacheName: nil)
            fetchResultController.delegate = self

            var error: NSError?

            var result = fetchResultController.performFetch(&error)
            backpackerSpots = fetchResultController.fetchedObjects as [BackpackerSpot]

            if result != true {
                println(error?.localizedDescription)
            }
        }





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

    // MARK: - Table view data source

    override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return self.backpackerSpots.count
    }


    override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {

        let cellIdentifier = "Cell"
        let cell = tableView.dequeueReusableCellWithIdentifier(cellIdentifier, forIndexPath: indexPath) as CustomTableViewCell


        let backpackerSpot = backpackerSpots[indexPath.row]
        cell.nameLabel.text = backpackerSpot.spotName
        cell.thumbnailImageView.image = UIImage(data: backpackerSpot.spotImage)
        cell.locationLabel.text = backpackerSpot.spotLocation


        return cell
    }

    override func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {


    }


    override func tableView(tableView: UITableView, editActionsForRowAtIndexPath indexPath: NSIndexPath) -> [AnyObject]? {


        var deleteAction = UITableViewRowAction(style: UITableViewRowActionStyle.Default, title: "Delete",handler: {
            (action:UITableViewRowAction!, indexPath:NSIndexPath!) -> Void in


//            if let managedObjectContext = (UIApplication.sharedApplication().delegate as AppDelegate).managedObjectContext {
           let entityBackpackerSpot = NSEntityDescription.entityForName("BackpackerSpot", inManagedObjectContext: self.coreDataStack.context)

                let backpackerSpotToDelete = self.fetchResultController.objectAtIndexPath(indexPath) as BackpackerSpot
                self.managedContext.deleteObject(backpackerSpotToDelete)

                var error: NSError?
                if !self.managedContext.save(&error) {
                    println("Could not save \(error), \(error?.description)")
                }


        })
        deleteAction.backgroundColor = UIColor(red: 169.0/255.0, green: 37.0/255.0, blue:
            50.0/255.0, alpha: 1.0)
        return [deleteAction]
    }

    func controllerWillChangeContent(controller: NSFetchedResultsController!) {
        tableView.beginUpdates()
    }

    func controller(controller: NSFetchedResultsController!, didChangeObject anObject: AnyObject!, atIndexPath indexPath: NSIndexPath!, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath!) {

        switch type {
        case .Insert:
            tableView.insertRowsAtIndexPaths([newIndexPath], withRowAnimation: .Fade)
        case .Delete:
            tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)
        case .Update:
            tableView.reloadRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)

        default:
            tableView.reloadData()
        }

        backpackerSpots = controller.fetchedObjects as [BackpackerSpot]
    }

    func controllerDidChangeContent(controller: NSFetchedResultsController!) {
        tableView.endUpdates()
    }

    override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject!) {
        if segue.identifier == "showBackpackerSpotDetails" {
            if let row = tableView.indexPathForSelectedRow()?.row {
                let destinationController = segue.destinationViewController as DetailViewController
                destinationController.backpackerSpot = backpackerSpots[row]
            }
        }
    }

    @IBAction func unwindToHomeScreen(segue: UIStoryboardSegue) {

    }

}

您尚未初始化 NSManagedContext 变量。使用默认的核心数据设置,您可以这样做:

 override func viewDidLoad() {
      if let appDelegate = UIApplication.sharedApplication().delegate as? AppDelegate {
          managedContext = appDelegate.managedObjectContext
      }
 }

编辑:鉴于您的核心数据设置,您应该这样做:

 override func viewDidLoad() {
      managedContext = coreDataStack.context
 }