Swift 2 migration saveContext() in appDelegate

10.1k views Asked by At

I have just downloaded the new Xcode 7.0 beta and did a migration from Swift 1.2 to Swift 2. The migration apparently did not change the whole code, in fact a method saveContext() which was fine until throws 2 errors for the line:

if moc.hasChanges && !moc.save() {

Binary operator '&&' cannot be applied to two Bool operands

and

Call can throw, but it is not marked with 'try' and the error is not handled

The method looks like this:

// MARK: - Core Data Saving support
func saveContext () {
    if let moc = self.managedObjectContext {
        var error: NSError? = nil
        if moc.hasChanges && !moc.save() {
            // 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()
        }
    }
}

Any ideas on how to get it working?

2

There are 2 answers

4
Mick MacCallum On BEST ANSWER

The first of the two errors you provided is misleading, but the second is spot on. The problem is in !moc.save() which as of Swift 2, no longer returns Bool and is instead annotated throws. This means that you you have to try this method and catch any exceptions that it may emit, instead of just checking wether its return value is true or false.

To reflect this, a new project created in Xcode 7 using Core Data will produce the following boilerplate code which can replace the code you're using.

func saveContext () {
    if managedObjectContext.hasChanges {
        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()
        }
    }
}
0
Ian On

The answer by 0x7fffffff is correct, but to improve upon Apple's boilerplate code, you can catch the specific error in the catch block using catch let error as NSError like so:

func saveContext () {
    if managedObjectContext.hasChanges {
        do {
            try managedObjectContext.save()
        } catch let error as NSError {

            NSLog("Unresolved error \(error), \(error.userInfo)")
            // Handle Error
        }
    }
}

The best practice is to use the var error witch will still be available if you just use it this way :

func saveContext () {
        if managedObjectContext.hasChanges {
            do {
                try managedObjectContext.save()
            } catch {
                NSLog("Unresolved error \(error), \(error.userInfo)")
                // Handle Error
            }
        }
    }

In the same way, if you are sure that managedObjectContext.save() will not throw an exception, the code is slimmed down to:

func saveContext () {
     if managedObjectContext.hasChanges {
        try! managedObjectContext.save()
     }
}

And to extrapolate on why managedObjectContext is not optional in the Swift 2 code, it is because the NSManagedObject(concurrencyType:) is an initializer that does not fail. In Xcode 6, the boilerplate code returned an optional context if the NSPersistentStoreCoordinator is nil, but you can handle this easily by checking.

lazy var managedObjectContext: NSManagedObjectContext = {
    let coordinator = self.persistentStoreCoordinator
    var moc = NSManagedObjectContext(concurrencyType: .MainQueueConcurrencyType)
    moc.persistentStoreCoordinator = coordinator
    return moc
}()