Book (Swift for Dummies) 第 4 章练习:致命错误:在展开 Optional 值时意外发现 nil

Posted

技术标签:

【中文标题】Book (Swift for Dummies) 第 4 章练习:致命错误:在展开 Optional 值时意外发现 nil【英文标题】:Book (Swift for Dummies) Chapter 4 Exercise: fatal error: unexpectedly found nil while unwrapping an Optional value 【发布时间】:2015-07-16 05:27:02 【问题描述】:

我正在学习swift,正在阅读《Swift for Dummies》一书,我试图按照作者的做法做书中的例子,但是我在第四章的例子中遇到了问题,你能各位帮帮我。

第 51 行的错误

MasterViewController.swift

newManagedObject.latitude = self.lastLocation.coordinate.latitude

线程 1:EXC_BAD_INSTRUCTION(代码=EXC_I386_INVOP,子代码=0x0)在 输出窗口显示致命错误:意外发现 nil 而 展开可选值 (lldb)

import UIKit
import CoreData
import CoreLocation

class MasterViewController: UITableViewController,
    NSFetchedResultsControllerDelegate,
    CLLocationManagerDelegate


    var detailViewController: DetailViewController? = nil
    var managedObjectContext: NSManagedObjectContext? = nil
    let locationManager = CLLocationManager()
    var lastLocation: CLLocation! = nil



    override func awakeFromNib() 
        super.awakeFromNib()
    

    override func viewDidLoad() 
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
        self.navigationItem.leftBarButtonItem = self.editButtonItem()

        let addButton = UIBarButtonItem(barButtonSystemItem: .Add, target: self, action: "insertNewObject:")
        self.navigationItem.rightBarButtonItem = addButton
        self.startSignificantChangeUpdates()
    

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

    func insertNewObject(sender: AnyObject) 
        let context = self.fetchedResultsController.managedObjectContext
        let entity = self.fetchedResultsController.fetchRequest.entity!
        let newManagedObject = NSEntityDescription.insertNewObjectForEntityForName("Event", inManagedObjectContext: context) as! Event

        // If appropriate, configure the new managed object.
        // Normally you should use accessor methods, but using KVC here avoids the need to add a custom class to the template.
        newManagedObject.latitude = self.lastLocation.coordinate.latitude
        newManagedObject.longitude = self.lastLocation.coordinate.longitude


        // Save the context.
        var error: NSError? = nil
        if !context.save(&error) 
            // 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.
            //println("Unresolved error \(error), \(error.userInfo)")
            abort()
        
    

    // MARK: - Segues

    override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) 
        if segue.identifier == "showDetail" 
            if let indexPath = self.tableView.indexPathForSelectedRow() 
            let object = self.fetchedResultsController.objectAtIndexPath(indexPath) as! NSManagedObject
            (segue.destinationViewController as! DetailViewController).detailItem = object
            
        
    

    // MARK: - Table View

    override func numberOfSectionsInTableView(tableView: UITableView) -> Int 
        return self.fetchedResultsController.sections?.count ?? 0
    

    override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int 
        let sectionInfo = self.fetchedResultsController.sections![section] as! NSFetchedResultsSectionInfo
        return sectionInfo.numberOfObjects
    

    override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell 
        let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as! UITableViewCell
        self.configureCell(cell, atIndexPath: indexPath)
        return cell
    

    override func tableView(tableView: UITableView, canEditRowAtIndexPath indexPath: NSIndexPath) -> Bool 
        // Return false if you do not want the specified item to be editable.
        return true
    

    override func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) 
        if editingStyle == .Delete 
            let context = self.fetchedResultsController.managedObjectContext
            context.deleteObject(self.fetchedResultsController.objectAtIndexPath(indexPath) as! NSManagedObject)

            var error: NSError? = nil
            if !context.save(&error) 
                // 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.
                //println("Unresolved error \(error), \(error.userInfo)")
                abort()
            
        
    

    func configureCell(cell: UITableViewCell, atIndexPath indexPath: NSIndexPath) 
            let object = self.fetchedResultsController.objectAtIndexPath(indexPath) as! Event
        cell.textLabel!.text = "latitude: " + object.latitude.description + " longitude: " + object.longitude.description
    

    // MARK: - Fetched results controller

    var fetchedResultsController: NSFetchedResultsController 
        if _fetchedResultsController != nil 
            return _fetchedResultsController!
        

        let fetchRequest = NSFetchRequest()
        // Edit the entity name as appropriate.
        let entity = NSEntityDescription.entityForName("Event", inManagedObjectContext: self.managedObjectContext!)
        fetchRequest.entity = entity

        // Set the batch size to a suitable number.
        fetchRequest.fetchBatchSize = 20

        // Edit the sort key as appropriate.
        let sortDescriptor = NSSortDescriptor(key: "timeStamp", ascending: false)
        let sortDescriptors = [sortDescriptor]

        fetchRequest.sortDescriptors = [sortDescriptor]

        // Edit the section name key path and cache name if appropriate.
        // nil for section name key path means "no sections".
        let aFetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: self.managedObjectContext!, sectionNameKeyPath: nil, cacheName: "Master")
        aFetchedResultsController.delegate = self
        _fetchedResultsController = aFetchedResultsController

        var error: NSError? = nil
        if !_fetchedResultsController!.performFetch(&error) 
             // 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. 
             //println("Unresolved error \(error), \(error.userInfo)")
             abort()
        

        return _fetchedResultsController!
        
    var _fetchedResultsController: NSFetchedResultsController? = nil

    func controllerWillChangeContent(controller: NSFetchedResultsController) 
        self.tableView.beginUpdates()
    

    func controller(controller: NSFetchedResultsController, didChangeSection sectionInfo: NSFetchedResultsSectionInfo, atIndex sectionIndex: Int, forChangeType type: NSFetchedResultsChangeType) 
        switch type 
            case .Insert:
                self.tableView.insertSections(NSIndexSet(index: sectionIndex), withRowAnimation: .Fade)
            case .Delete:
                self.tableView.deleteSections(NSIndexSet(index: sectionIndex), withRowAnimation: .Fade)
            default:
                return
        
    

    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:
                self.configureCell(tableView.cellForRowAtIndexPath(indexPath!)!, atIndexPath: indexPath!)
            case .Move:
                tableView.deleteRowsAtIndexPaths([indexPath!], withRowAnimation: .Fade)
                tableView.insertRowsAtIndexPaths([newIndexPath!], withRowAnimation: .Fade)
            default:
                return
        
    

    func controllerDidChangeContent(controller: NSFetchedResultsController) 
        self.tableView.endUpdates()
    

    /*
     // Implementing the above methods to update the table view in response to individual changes may have performance implications if a large number of changes are made simultaneously. If this proves to be an issue, you can instead just implement controllerDidChangeContent: which notifies the delegate that all section and object changes have been processed.

     func controllerDidChangeContent(controller: NSFetchedResultsController) 
         // In the simplest, most efficient, case, reload the table view.
         self.tableView.reloadData()
     
     */

    // MARK: CLLocationManagerDelegate Protocol
    func startSignificantChangeUpdates () 

        if CLLocationManager.authorizationStatus() == CLAuthorizationStatus.NotDetermined 

            self.locationManager.requestWhenInUseAuthorization()
        

        if CLLocationManager.locationServicesEnabled() 
            self.locationManager.delegate = self

            self.locationManager.distanceFilter = kCLDistanceFilterNone
            self.locationManager.desiredAccuracy = kCLLocationAccuracyBest
            self.locationManager.startUpdatingLocation()

            self.locationManager.startMonitoringSignificantLocationChanges()
        

    
    func locationManager (manager: CLLocationManager!, didUpdateLocations locations:[AnyObject]!) 

        self.lastLocation = manager.location
        let eventDate = self.lastLocation.timestamp;
        let howRecent = eventDate.timeIntervalSinceNow;


    

    func locationManager(manager: CLLocationManager!, didFailWithError error: NSError!) 
        // need to add code to catch errors
    





import UIKit
import MapKit

class DetailViewController: UIViewController 

    @IBOutlet var mapView: MKMapView!

    @IBOutlet weak var detailDescriptionLabel: UILabel!


    var detailItem: AnyObject? 
        didSet 
            // Update the view.
            self.configureView()
        
    

    func configureView() 
        // Update the user interface for the detail item.
        if let detail: AnyObject = self.detailItem 
            if let label = self.detailDescriptionLabel 
                label.text = detail.valueForKey("timeStamp")!.description
            
        
    

    override func viewDidLoad() 
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
        self.configureView()

    

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





import Foundation
import CoreData

class Event: NSManagedObject 

    @NSManaged var latitude: NSNumber
    @NSManaged var longitude: NSNumber
    @NSManaged var timeStamp: NSDate




import UIKit
import CoreData

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate 

    var window: UIWindow?


    func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool 
        // Override point for customization after application launch.
        let navigationController = self.window!.rootViewController as! UINavigationController
        let controller = navigationController.topViewController as! MasterViewController
        controller.managedObjectContext = self.managedObjectContext
        return true
    

    func applicationWillResignActive(application: UIApplication) 
        // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
        // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
    

    func applicationDidEnterBackground(application: UIApplication) 
        // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
        // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
    

    func applicationWillEnterForeground(application: UIApplication) 
        // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background.
    

    func applicationDidBecomeActive(application: UIApplication) 
        // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
    

    func applicationWillTerminate(application: UIApplication) 
        // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
        // Saves changes in the application's managed object context before the application terminates.
        self.saveContext()
    

    // MARK: - Core Data stack

    lazy var applicationDocumentsDirectory: NSURL = 
        // The directory the application uses to store the Core Data store file. This code uses a directory named "com.sauer.Locatapp" in the application's documents Application Support directory.
        let urls = NSFileManager.defaultManager().URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask)
        return urls[urls.count-1] as! NSURL
    ()

    lazy var managedObjectModel: NSManagedObjectModel = 
        // The managed object model for the application. This property is not optional. It is a fatal error for the application not to be able to find and load its model.
        let modelURL = NSBundle.mainBundle().URLForResource("Locatapp", withExtension: "momd")!
        return NSManagedObjectModel(contentsOfURL: modelURL)!
    ()

    lazy var persistentStoreCoordinator: NSPersistentStoreCoordinator? = 
        // The persistent store coordinator for the application. This implementation creates and return a coordinator, having added the store for the application to it. This property is optional since there are legitimate error conditions that could cause the creation of the store to fail.
        // Create the coordinator and store
        var coordinator: NSPersistentStoreCoordinator? = NSPersistentStoreCoordinator(managedObjectModel: self.managedObjectModel)
        let url = self.applicationDocumentsDirectory.URLByAppendingPathComponent("Locatapp.sqlite")
        var error: NSError? = nil
        var failureReason = "There was an error creating or loading the application's saved data."
        if coordinator!.addPersistentStoreWithType(NSSQLiteStoreType, configuration: nil, URL: url, options: nil, error: &error) == nil 
            coordinator = nil
            // Report any error we got.
            var dict = [String: AnyObject]()
            dict[NSLocalizedDescriptionKey] = "Failed to initialize the application's saved data"
            dict[NSLocalizedFailureReasonErrorKey] = failureReason
            dict[NSUnderlyingErrorKey] = error
            error = NSError(domain: "YOUR_ERROR_DOMAIN", code: 9999, userInfo: dict)
            // Replace this 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.
           NSLog("Unresolved error \(error), \(error!.userInfo)")
            abort()
        

        return coordinator
    ()

    lazy var managedObjectContext: NSManagedObjectContext? = 
        // Returns the managed object context for the application (which is already bound to the persistent store coordinator for the application.) This property is optional since there are legitimate error conditions that could cause the creation of the context to fail.
        let coordinator = self.persistentStoreCoordinator
        if coordinator == nil 
            return nil
        
        var managedObjectContext = NSManagedObjectContext()
        managedObjectContext.persistentStoreCoordinator = coordinator
        return managedObjectContext
    ()

    // MARK: - Core Data Saving support

    func saveContext () 
        if let moc = self.managedObjectContext 
            var error: NSError? = nil
            if moc.hasChanges && !moc.save(&error) 
                // 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.
                NSLog("Unresolved error \(error), \(error!.userInfo)")
                abort()
            
        
    


【问题讨论】:

因为self.lastLocation.coordinate.latitude中没有值。请检查您是否获得任何位置。 【参考方案1】:

此变量为空

self.lastLocation

你应该调用这个函数

func locationManager (manager: CLLocationManager!, didUpdateLocations locations:[AnyObject]!) 

在某处初始化 lastLocation。所以它不会为空

或者你可以改变

var lastLocation: CLLocation! = nil

到这里

var lastLocation: CLLocation? = nil

所以当运行这段代码时

newManagedObject.latitude = self.lastLocation?.coordinate.latitude

它将跳过这一行,因为 self.lastLocation 为 null 而不是显示 EXC_BAD_INSTRUCTION 错误

【讨论】:

应用程序启动,但我没有得到位置...当我点击 + 按钮时,他关闭并给出此错误...我按照你告诉我的做了...但我仍然得到一个问题...现在换一个...我将这一行更改为 var lastLocation: CLLocation? = nil 而这个是 newManagedObject.latitude = self.lastLocation?.coordinate.latitude 我得到了这个错误:可选类型'CLLocationDegrees?'的值没有打开你的意思是使用'!'或者 '?'?修改后...应用程序不再启动。 在调用这个newManagedObject.latitude = self.lastLocation.coordinate.latitude之前你有没有调用locationManager的构造函数? 我正在学习 Swift... 怎么调用构造函数???也许我打过电话,但我不确定,因为我不知道该怎么做……

以上是关于Book (Swift for Dummies) 第 4 章练习:致命错误:在展开 Optional 值时意外发现 nil的主要内容,如果未能解决你的问题,请参考以下文章

SLAM for dummies中文翻译

什么是循环冗余检查以及它是如何简单地工作的(for-dummies 风格)?

链资料Blockchain For Dummies电子书

如何在 pyqt 中嵌入 matplotlib - For Dummies

如何在 pyqt 中嵌入 matplotlib - For Dummies

如何在 pyqt 中嵌入 matplotlib - For Dummies