PixelStack PixelStack - 1 month ago 8
Swift Question

Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '+[NSKeyedArchiver unarchiveObjectWithFile:]

I'm following the FoodTracker demo at developer apple. I'm on the last section: Persist data.

I'm running Xcode 8, and following the demo building on Swift 3.

If I download the code and convert to Swift 2.3, it doesn't work. If I convert to Swift 3.0 it doesn't work either. I have been trying to follow the demo on Swift 3.

The code requires some changes in order to compile and run on Swift 3, and I have been successful so far.

However, I'm now getting this at runtime:

FoodTracker[1597:720222] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '+[NSKeyedArchiver unarchiveObjectWithFile:]: unrecognized selector sent to class 0x1081497a8'

The changes I had do for this section were:
1) On the Meal class I added two static constants:

static let DocumentsDirectory =
FileManager().urls(for: .documentDirectory, in:
.userDomainMask).first!
static let ArchiveURL = DocumentsDirectory.appendingPathComponent("meals")


2) Also on the Meal class I added a struct and a convenience init:

func encode(with aCoder: NSCoder) {
aCoder.encode(name, forKey: PropertyKey.nameKey)
aCoder.encode(photo, forKey: PropertyKey.photoKey)
aCoder.encode(rating, forKey: PropertyKey.ratingKey)
}

required convenience init?(coder aDecoder: NSCoder) {
let name = aDecoder.decodeObject(forKey: PropertyKey.nameKey) as! String

// Because photo is an optional property of Meal, use conditional cast
let photo = aDecoder.decodeObject(forKey: PropertyKey.photoKey) as? UIImage

let rating = aDecoder.decodeInteger(forKey: PropertyKey.ratingKey)

// Must call designated initializer
self.init(name: name, photo: photo, rating: rating)
}


3) On MealTableViewController class I added functions saveMeals and loadMeals:

func saveMeals() {
let isSuccessfulSave = NSKeyedArchiver.archiveRootObject(meals, toFile: Meal.ArchiveURL.path)
if !isSuccessfulSave {
print("Failed to save meals...")
}
}

func loadMeals() -> [Meal]? {
return NSKeyedArchiver.classForKeyedUnarchiver().unarchiveObject(withFile: Meal.ArchiveURL.path) as? [Meal]
}


The code compiles successfully but crashes at runtime. Line 5 of the error stacktrace seem to point to the Meal class and the loadMeals method as the culprit:

5 FoodTracker 0x0000000107ce9e44 _TFC11FoodTracker23MealTableViewController9loadMealsfT_GSqGSaCS_4Meal__ + 276
6 FoodTracker 0x0000000107ce701a _TFC11FoodTracker23MealTableViewController11viewDidLoadfT_T_ + 266
7 FoodTracker 0x0000000107ce7222 _TToFC11FoodTracker23MealTableViewController11viewDidLoadfT_T_ + 34


Unfortunately I don't know more at this point to follow the breadcrumbs that Xcode and Swift are leaving behind for me. Just trying to learn from the source (Apple Developers).

I've done searching on the web and on this site but haven't quite found an answer that I've been able to use to solve the problem. This is the last section of the tutorial and I'd really would like to complete it. Thank you for any assistance you can provide; I hope to pay it forward once I become proficient on Swift. Thank you!

Answer

Use NSKeyedUnarchiver:

func loadMeals() -> [Meal]? {
    return NSKeyedUnarchiver.unarchiveObject(withFile: Meal.ArchiveURL.path) as? [Meal]
}