iosswiftcore-datacrudnskeyedarchiver

NSKeyedUnarchiver error in Swift Core Data


I have this core data issue: enter image description here

*** -[NSKeyedUnarchiver decodeObjectForKey:]: value for key (id) is not an object. This will become an error in the future.

First I would like to describe what I am trying to do: I perform a GET request to my API, get an array of elements and add the data I got to Core Data.

Here is my CoreData Entity:

extension PetEntity {

    @nonobjc public class func fetchRequest() -> NSFetchRequest<PetEntity> {
        return NSFetchRequest<PetEntity>(entityName: "PetEntity")
    }

    @NSManaged public var category: CategoryDTO?
    @NSManaged public var id: Int64
    @NSManaged public var name: String?
    @NSManaged public var photoUrls: [String]?
    @NSManaged public var status: String?
    @NSManaged public var tags: [TagDTO]?

}

extension PetEntity : Identifiable {

}

My DTO I use to get Json data in and then map it to my model:

struct PetDTO: Codable {
    var id: Int
    var category: CategoryDTO?
    var name: String?
    var photoUrls: [String]?
    var tags: [TagDTO]?
    var status: StatusDTO
}

public class CategoryDTO: NSObject, Codable {
    var id: Int
    var name: String?
    
    public enum CodingKeys: String, CodingKey {
        case id
        case name
    }
    
    public required init?(coder: NSCoder) {
        id = coder.decodeObject(forKey: CodingKeys.id.rawValue) as? Int ?? 0 // error here
        name = coder.decodeObject(forKey: CodingKeys.name.rawValue) as? String
    }
}

extension CategoryDTO: NSSecureCoding{
    public static var supportsSecureCoding: Bool{
        return true
    }
    public func encode(with coder: NSCoder) {
        coder.encode(id, forKey: CodingKeys.id.rawValue)
        coder.encode(name, forKey: CodingKeys.name.rawValue)
    }
}

@objc(CategoryDtoTransformer)
public final class CategoryDtoTransformer: NSSecureUnarchiveFromDataTransformer {
    public static let name = NSValueTransformerName(rawValue: String(describing: CategoryDtoTransformer.self))
    public override static var allowedTopLevelClasses: [AnyClass] {
        return [CategoryDTO.self, NSString.self, NSArray.self]
    }

    @objc dynamic
    public static func register() {

        let transformer = CategoryDtoTransformer()
        ValueTransformer.setValueTransformer(transformer, forName: name)
    }
}

public class TagDTO: NSObject, Codable {
    var id: Int
    var name: String?
    
    public enum CodingKeys: String, CodingKey {
        case id
        case name
    }
    
    public required init?(coder: NSCoder) {
        id = coder.decodeObject(forKey: CodingKeys.id.rawValue) as? Int ?? 0 // Error here
        name = coder.decodeObject(forKey: CodingKeys.name.rawValue) as? String
    }
}

extension TagDTO: NSSecureCoding{
    public static var supportsSecureCoding: Bool{
        return true
    }
    public func encode(with coder: NSCoder) {
        coder.encode(id, forKey: CodingKeys.id.rawValue)
        coder.encode(name, forKey: CodingKeys.name.rawValue)
    }
}

@objc(TagDtoTransformer)
public final class TagDtoTransformer: NSSecureUnarchiveFromDataTransformer {
    public static let name = NSValueTransformerName(rawValue: String(describing: TagDtoTransformer.self))
    public override static var allowedTopLevelClasses: [AnyClass] {
        return [TagDTO.self, NSString.self, NSArray.self]
    }

    @objc dynamic
    public static func register() {

        let transformer = TagDtoTransformer()
        ValueTransformer.setValueTransformer(transformer, forName: name)
    }
}

enum StatusDTO: String, Codable {
    case available
    case sold
    case pending
}

And here is my CRUD code for adding to database:

class CoreDataPetPersistance: PetPerstitenceProtocol {
    
    let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
    
    func add(pet: PetDTO) {
        let newPet = PetEntity(context: self.context)
        newPet.id = Int64(pet.id)
        newPet.name = pet.name
        newPet.category = pet.category
        newPet.photoUrls = pet.photoUrls
        newPet.tags = pet.tags
        newPet.status = pet.status.rawValue
        
        do {
            try self.context.save()
        } catch {
            print(error)
        }
    }
 }

Also I have added constraints to my ID to make each pet I get from API unique and registered CategoryDTO and TagDTO in app delegate. It should be fine.

Full error I get:

CoreData: error: -executeRequest: encountered exception = Error Domain=NSCocoaErrorDomain Code=133000 "Attempt to access an object not found in store." UserInfo={objectID=0xa57351074a65fcb3 <x-coredata://FEB02FEE-9B8B-4785-82A1-263F82D4CDBC/PetEntity/p1095>} with userInfo = {
    NSCocoaErrorDomain = 133000;
    NSUnderlyingError = "Error Domain=NSCocoaErrorDomain Code=133000 \"Attempt to access an object not found in store.\" UserInfo={objectID=0xa57351074a65fcb3 <x-coredata://FEB02FEE-9B8B-4785-82A1-263F82D4CDBC/PetEntity/p1095>}";
}

Please help me fix this and explain why this error happens

Thanks


Solution

  • There are dedicated APIs for scalar types

    id = coder.decodeInt64(forKey: CodingKeys.id.rawValue)
    

    Have a look for all available APIs

    To match the types declare id as Int64 or convert the type.