3
votes

So I have an array of custom objects. When I try to save the array to NSUserDefaults, I have to archive them. Here is what I am doing to archive the array of my custom objects :

override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {

    //3

    var allMessages = defaults.objectForKey(UserDefaultsMesssageKey) as! [AddMessageViewController.harassText]
    var aMessage = AddMessageViewController.harassText(phoneNumber: 0, message: "1", frequency: 1, active: 1)
    allMessages.append(aMessage)
    saveMessage(allMessages)


    return (allMessages.count)
}

func archiveMessage(message:[AddMessageViewController.harassText]) -> NSData {
    let archivedObject = NSKeyedArchiver.archivedDataWithRootObject(message as NSArray)
    return archivedObject
}
func saveMessage(messages: [AddMessageViewController.harassText]) {
    let archivedObject = archiveMessage(messages)
    defaults.setObject(archivedObject, forKey: UserDefaultsMesssageKey)
    defaults.synchronize()
}
func retrieveData()-> [AddMessageViewController.harassText]? {
    if let unarchiveObject = NSUserDefaults.standardUserDefaults().objectForKey(UserDefaultsMesssageKey) as? NSData {
        return NSKeyedUnarchiver.unarchiveObjectWithData(unarchiveObject) as? [AddMessageViewController.harassText]
    }
    return nil
}

The flow is :

  1. On saveMessage call, pass the custom array in to get archived. Here is what the array looks like to GDB. You can see that it is an array, and it has 1 class object contained in it.

    ([Harass_Your_Kate.AddMessageViewController.harassText]) $R0 = 1 value { [0] = 0x00007fa5ab409600 { ObjectiveC.NSObject = { isa = Harass_Your_Kate.AddMessageViewController.harassText } phoneNumber = 0 message = "1" frequency = 1 active = 1 } }

  2. The array gets passed into archiveMessage. The code immediately fails on the NSKeyedArchiver method. The Error message is as follows :

    [_TtCC16Harass_Your_Kate24AddMessageViewController10harassText encodeWithCoder:]: unrecognized selector sent to instance 0x7fa5ab409600
    

    2016-07-15 16:31:26.019 Harass Your Kate[5858:7847495] * -[NSKeyedArchiver dealloc]: warning: NSKeyedArchiver deallocated without having had -finishEncoding called on it. 2016-07-15 16:31:26.023 Harass Your Kate[5858:7847495] * Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[_TtCC16Harass_Your_Kate24AddMessageViewController10harassText encodeWithCoder:]: unrecognized selector sent to instance 0x7fa5ab409600

I appreciate all of the help, thank you in advance!

EDIT: Here is the definition of my object:

class Text : NSObject {
    var phoneNumber = 0             //Text address to send to
    var message: String = ""        //Message to be sent
    var frequency = 24              //Number of hours between messages (usually a multiple of 24 - 24 = daily)
    var active = 0                  // 0 if cancelled, 1 if active

    init(phoneNumber: Int, message: String, frequency: Int, active: Int ){
        self.phoneNumber = phoneNumber
        self.message = message
        self.frequency = frequency
        self.active = active
    }
}
1
We need to see the NSCoding methods for your custom object. (initWithCoder and encodeWithCoder). - Duncan C
I don't have either of those things. - Code Wiget
That's the problem then. Show us the definition of your custom class then. (Edit your question to add the code, don't post it in a comment.) - Duncan C
You can't use NSCoding to convert an "object graph" to NSData unless every object conforms to NSCoding (which means that it has to implement initWithCoder and encodeWithCoder.) If you try to encode a container (like an array or dictionary) of objects, all the objects in the container must conform to NSCoding or it won't work. - Duncan C
@DuncanC Thank you for your help. I included in my edit the definition of my custom class. I am very confused about what you mean with NSCoding, as I have never used that before, and certainly not in the context of saving a custom class array. But again, thank you for taking the time to help me. I look forward to hearing from you again - Code Wiget

1 Answers

3
votes

NSKeyedArchiver serializes objects by taking advantage of methods you have to provide by conforming to the NSCoding protocol. It can't automatically infer how to save your custom classes; you have to implement that yourself as follows:

class Text : NSObject, NSCoding {
    var phoneNumber = 0             //Text address to send to
    var message: String = ""        //Message to be sent
    var frequency = 24              //Number of hours between messages (usually a multiple of 24 - 24 = daily)
    var active = 0                  // 0 if cancelled, 1 if active

    init(phoneNumber: Int, message: String, frequency: Int, active: Int ){
        self.phoneNumber = phoneNumber
        self.message = message
        self.frequency = frequency
        self.active = active
    }

    required init?(coder aDecoder: NSCoder) {
        phoneNumber = aDecoder.decodeInteger(forKey: "phoneNumber")
        message = aDecoder.decodeObject(forKey: "message") as! String
        frequency = aDecoder.decodeInteger(forKey: "frequency")
        active = aDecoder.decodeInteger(forKey: "active")
    }

    func encode(with aCoder: NSCoder) {
        aCoder.encode(phoneNumber, forKey: "phoneNumber")
        aCoder.encode(message, forKey: "message")
        aCoder.encode(frequency, forKey: "frequency")
        aCoder.encode(active, forKey: "active")
    }
}

As you can see, the initializer reads from the coder and creates and instance, whereas the encode method saves relevant information to the coder. In a simple class like this, their implementations may seem obvious, but in a more complex custom type, there are non-trivial encoding decisions to make.