5

I have a CoreData Entity SavedWorkout. It has the following attributes: enter image description here

completionCounter is an array of Bool, and workout is a custom class called Workout.

I am saving my data like so:

let saveCompletionCounter = currentCompletionCounter
let saveDate = Date() as NSDate
let saveRoutineIndex = Int16(currentWorkoutRoutine)
let saveWorkout = NSKeyedArchiver.archivedData(withRootObject: workout)

item.setValue(saveDate, forKey: "date")
item.setValue(saveWorkout, forKey: "workout")
item.setValue(saveRoutineIndex, forKey: "routineIndex")
item.setValue(saveCompletionCounter, forKey: "completionCounter")

do {
  try moc.save()
  print("save successful")
} catch {
  print("saving error")
}

where moc is an instance of NSManagedObjectContext, and item is an instance of NSManagedObject:

moc = appDelegate.managedObjectContext
entity = NSEntityDescription.entity(forEntityName: "SavedWorkout", in: moc)!
item = NSManagedObject(entity: entity, insertInto: moc)

In accordance with this and this and this , I have made my Workout class conform to NSObject and NSCoding, so it now looks like this:

class Workout: NSObject, NSCoding {

  let name: String
  let imageName: String
  let routine: [WorkoutRoutine]
  let shortDescription: String

  required init?(coder aDecoder: NSCoder) {
    name = aDecoder.decodeObject(forKey: "name") as! String
    imageName = aDecoder.decodeObject(forKey: "imageName") as! String
    routine = aDecoder.decodeObject(forKey: "routine") as! [WorkoutRoutine]
    shortDescription = aDecoder.decodeObject(forKey: "shortDescription") as! String
  }

  func encode(with aCoder: NSCoder) {
    aCoder.encode(name, forKey: "name")
    aCoder.encode(imageName, forKey: "imageName")
    aCoder.encode(routine, forKey: "routine")
    aCoder.encode(shortDescription, forKey: "shortDescription")
  }

  init(name: String, imageName: String, routine: [WorkoutRoutine], shortDescription: String) {
    self.name = name
    self.imageName = imageName
    self.routine = routine
    self.shortDescription = shortDescription
  }
}

However I always get an error on the line routine: aDecoder.decodeObject....

The error says:

NSForwarding: warning: object 0x60800002cbe0 of class 'App.WorkoutRoutine' does not implement methodSignatureForSelector: -- trouble ahead

Unrecognized selector -[FitLift.WorkoutRoutine replacementObjectForKeyedArchiver:]

Why does this give me an error and not the other Transformable attribute? How do I save a custom class as a property of a CoreData entity?

A_toaster
  • 1,196
  • 3
  • 22
  • 50

1 Answers1

5

The issue is that WorkoutRoutine is itself a custom class and as of your error it is not NSCoding compliant, therefore aCoder.encode(routine, forKey: "routine") doesn't really know how to encode it, as well as routine = aDecoder.decodeObject(forKey: "routine") as! [WorkoutRoutine] doesn't know how to decode it.

Not really related, but please try a safer approach for your coder and encoder initializer as the force unwrap might cause crashes if the encoder does not contain the keys you are looking for (for any reason)

required init?(coder aDecoder: NSCoder) {
    guard let name = aDecoder.decodeObject(forKey: "name") as? String,
        let imageName = aDecoder.decodeObject(forKey: "imageName") as? String,
        let routine = aDecoder.decodeObject(forKey: "routine") as? [WorkoutRoutine],
        let shortDescription = aDecoder.decodeObject(forKey: "shortDescription") as? String else {
            return nil
    }
    self.name = name
    self.imageName = imageName
    self.routine = routine
    self.shortDescription = shortDescription
}
Giuseppe Lanza
  • 3,519
  • 1
  • 18
  • 40
  • Thanks for your answer! One of the issues I've been having is trying to use Swift 3 and Xcode 8 while maintaining compatibility with iOS 9. What would an alternative way be to save custom classes as a coredata entity be? – A_toaster Jul 21 '17 at 07:08
  • 1
    For sure having a core data model that reflects your class would be a best practice. You can connect your models through relations. In your example the workout property would be a relation one to one to an entity "workout" which would contain the properties of your workout class where the routine property would be a relation one to many to a workoutRoutine entity. https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/CoreData/KeyConcepts.html – Giuseppe Lanza Jul 21 '17 at 09:45
  • 1
    If you want to use NSCoder consider a document based persistence layer. CoreData might not be the best choice for you. If you want to use core data I strongly recommend to use it for what it is its main purpose: a relational database. – Giuseppe Lanza Jul 21 '17 at 09:47