5

When try to encode my custom object in iOS swift get this error from Xcode 8.3

unrecognized selector sent to instance 0x60800166fe80 *** -[NSKeyedArchiver dealloc]: warning: NSKeyedArchiver deallocated without having had -finishEncoding called on it.

And my code like this:

import UIKit import Foundation

class Place: NSObject {

    func setCustomObject(CustomObject obj:Any,Key key:String) {

        let encodedObject : Data = NSKeyedArchiver.archivedData(withRootObject: obj)
        UserDefaults.standard.set(encodedObject, forKey: key)

    }
}
reza_khalafi
  • 6,230
  • 7
  • 56
  • 82

2 Answers2

4

Here's an example how to make an object to conform to NSCoding. Basically you need to provide implementation of two methods - required convenience init?(coder decoder: NSCoder) and encode(with aCoder: NSCoder)

class Book: NSObject, NSCoding {
  var title: String?
  var pageCount: Int?

  // Memberwise initializer
  init(title: String,pageCount: Int) {
   self.title = title
   self.pageCount = pageCount
  }

  // MARK: NSCoding


  // Here you will try to initialize an object from archve using keys you did set in `encode` method.
  required convenience init?(coder decoder: NSCoder) {
    guard let title = decoder.decodeObject(forKey: "title") as? String else { return nil }

    self.init(title: title, pageCount: decoder.decodeInteger(forKey: "pageCount"))
  }

  // Here you need to set properties to specific keys in archive
  func encode(with aCoder: NSCoder) {
    aCoder.encode(self.title, forKey: "title")
    aCoder.encodeCInt(Int32(self.pageCount), forKey: "pageCount")
  }
}

Also I would recommend changing your setCustomObject method to this:

func setCustomObject(obj:NSCoding, key:String) {
  let encodedObject : Data = NSKeyedArchiver.archivedData(withRootObject: obj)
  UserDefaults.standard.set(encodedObject, forKey: key)
}

This way compiler prevent you passing NSKeyedArchiver an object that does not conform to NSCoding protocol.

If you don't want to provide all properties in the init method you can use default values:

init(title : String? = nil, pageCount: Int? = nil){
  self.title = title
  self.pageCount = pageCount
}

Now you can just init your object without any properties. Like that Book()

Juri Noga
  • 4,363
  • 7
  • 38
  • 51
  • I have major problem with this example, My object has too much property and now if every time i want to instance new object must init object with all property that is not nesesarry :| @njuri – reza_khalafi Apr 18 '17 at 05:26
  • I want my new instance of object contains nil properties @njuri – reza_khalafi Apr 18 '17 at 05:34
  • Already using RMMAper in Objective C it was so easy for using but new is so complicated and not usable @njuri – reza_khalafi Apr 18 '17 at 05:45
  • 1
    Make your properties Optional if they aren't yet and profile default `nil` value to init method. See updated answer. – Juri Noga Apr 18 '17 at 09:03
  • Thanks but now when try to retrieve my object from userDefault i getting this error: fatal error: unexpectedly found nil while unwrapping an Optional value @njuri – reza_khalafi Apr 18 '17 at 09:51
  • 1
    You told that you'd like to properties to be `nil` - that's what the error tells you. – Juri Noga Apr 18 '17 at 10:05
  • yes but when made new instance with nil properties i will set some values in properties and check my object when try to set in user default and it's correct but when try to retrieve it get the error @njuri – reza_khalafi Apr 18 '17 at 10:07
3

Here is the solutions, you have to implement the two methods

Encode Method

func encode(with aCoder: NSCoder)

Decoding method

 required init?(coder aDecoder: NSCoder)

Complete Example code

class User: NSObject , NSCoding
{


var userID : Int = 0
var name : String = ""
var firstName : String = ""
var lastName : String = ""
var username : String = ""
var email : String = ""

override init(){
    super.init();
}

func encode(with aCoder: NSCoder) {

    aCoder.encode(self.userID,  forKey: "id");
    aCoder.encode(self.firstName,    forKey: "first_name");
    aCoder.encode(self.lastName,    forKey: "last_name");
    aCoder.encode(self.name,    forKey: "name");
    aCoder.encode(self.username,forKey: "username");
    aCoder.encode(self.email,   forKey: "email");
}

required init?(coder aDecoder: NSCoder) {
    super.init()

    self.userID     = aDecoder.decodeInteger(forKey: "id");
    self.firstName  = aDecoder.decodeObject(forKey: "first_name") as! String;
    self.lastName   = aDecoder.decodeObject(forKey: "last_name") as! String;
    self.name       = aDecoder.decodeObject(forKey: "name") as! String
    self.username   = aDecoder.decodeObject(forKey: "username") as! String
    self.email      = aDecoder.decodeObject(forKey: "email") as! String;
}

init(data : [String: AnyObject]) {

    super.init()

    self.userID = String.numberValue(data["user_id"]).intValue;
    self.firstName = String.stringValue(data["first_name"]);
    self.lastName = String.stringValue(data["last_name"]);
    self.email = String.stringValue(data["email"]);
    self.username = String.stringValue(data["user_name"]);
}

class func loadLoggedInUser()  -> User {

    if let  archivedObject = UserDefaults.standard.object(forKey:"CurrentUserAcc"){

        if let user  = NSKeyedUnarchiver.unarchiveObject(with: (archivedObject as! NSData) as Data) as? User {

            return user;
        }

    }

    return User()
}

func saveUser(){

    let archivedObject : NSData = NSKeyedArchiver.archivedData(withRootObject: self) as NSData

    UserDefaults.standard.set(archivedObject, forKey: "CurrentUserAcc");

    UserDefaults.standard.synchronize();
}

func deleteUser(){

    UserDefaults.standard.set(nil, forKey: "CurrentUserAcc")

    UserDefaults.standard.synchronize();
} 
}
junaidsidhu
  • 3,539
  • 1
  • 27
  • 49