Search code examples
swiftcodablenscodingnskeyedarchivernskeyedunarchiver

NSKeyedUnarchiver decodeObjectForKey: cannot decode object of class for key (NS.objects)


I looked through whole SO but still no answer. My app reports this problem:

Fatal Exception: NSInvalidUnarchiveOperationException *** -[NSKeyedUnarchiver decodeObjectForKey:]: cannot decode object of class (App_Title.Products) for key (NS.objects); the class may be defined in source code or a library that is not linked

I did already do NSKeyedUnarchiver.setClass just before #unarchiveObject:

func loadProducts() -> [Products]? {
    NSKeyedUnarchiver.setClass(Products.self, forClassName: "Products")
    let unarchivedData = NSKeyedUnarchiver.unarchiveObject(withFile: Products.ArchiveURL.path)

My Products class begins with @Objc:

import Foundation

@objc(Products)
class Products: NSObject, Codable, NSCoding { ... }

Adding the two lines above which seemed to help people didn't bring me any luck. Before them and after them it's the same behaviour. I personally could never reproduce this issue myself.

During development I keep very closely to the app guide on peristance and reviewed it multiple times.

Just before NSKeyedArchiver I check for file existance:

let filePath = Products.ArchiveURL.path
let fileManager = FileManager.default
if fileManager.fileExists(atPath: filePath) {

Here some additional informations in screenshots.

The only place where I could find a real exception description was Firebase Crashalytics:

enter image description here

A screenshot from the Xcode Organizer under the Crash tab:

enter image description here

Which leads to this line in the code:

NSKeyedUnarchiver.setClass(Products.self, forClassName: "Products")
let unarchivedData = NSKeyedUnarchiver.unarchiveObject(withFile: Products.ArchiveURL.path)

enter image description here

Products class with @Objc annotation.

enter image description here


Solution

  • It seems that you are mixing up Codable and NSCoding. Don't try to use both simultaneously. NSKeyedUnarchiver belongs to NSCoding.

    Your class contains property list compliant properties. Drop NSCoding and use only Codable. (By the way it's recommended to name the class in singular form Product).

    Delete everything which is related to NSCoding including the protocol conformance and with Codable it's not necessary that the class must inherit from NSObject (the object can be even a struct).

    The loadProducts function can be reduced to

    func loadProducts() throws -> [Product] {
        let data = try Data(contentsOf: Product.ArchiveURL)
        return try PropertyListDecoder().decode([Product].self, from: data)
    }
    

    It's good practice to hand over thrown errors to the caller

    And delete the CodingKeys, you don't need them if the keys match the property names.