Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

App crashes when storing custom object in NSUserDefaults

Tags:

xcode

ios

swift

I have custom class named User witch conforms to NSCoding protocol. But when I'm trying to store it in NSUserDefaults I get this error:

Property list invalid for format: 200 
(property lists cannot contain objects of type 'CFType')

And this warning:

NSForwarding: warning: object 0x7f816a681110 of class 'Shikimori.User'
does not implement methodSignatureForSelector: -- trouble ahead
Unrecognized selector -[Shikimori.User _copyDescription]

User class:

class User: NSCoding {

private enum CoderKeys: String {
    case ID = "user.id"
    case Username = "user.username"
    case Email = "user.email"
    case Avatar = "user.avatar"
}

let id: Int
let username: String
let email: String
let avatar: String

init(id: Int, username: String, email: String, avatar: String) {
    self.id = id
    self.username = username
    self.email = email
    self.avatar = avatar
}

required init(coder aDecoder: NSCoder) {
    id = aDecoder.decodeIntegerForKey(CoderKeys.ID.rawValue)
    username = aDecoder.decodeObjectForKey(CoderKeys.Username.rawValue) as String
    email = aDecoder.decodeObjectForKey(CoderKeys.Email.rawValue) as String
    avatar = aDecoder.decodeObjectForKey(CoderKeys.Avatar.rawValue) as String
}

func encodeWithCoder(aCoder: NSCoder) {
    aCoder.encodeInteger(id, forKey: CoderKeys.ID.rawValue)
    aCoder.encodeObject(username as NSString, forKey: CoderKeys.Username.rawValue)
    aCoder.encodeObject(email as NSString, forKey: CoderKeys.Email.rawValue)
    aCoder.encodeObject(avatar as NSString, forKey: CoderKeys.Avatar.rawValue)
}
}

Update Saving code:

let userKeyForDefaults = "apimanager.user"


let defaults = NSUserDefaults.standardUserDefaults()
defaults.setObject(user, forKey: self.userKeyForDefaults)
like image 762
rabbitinspace Avatar asked Sep 28 '22 23:09

rabbitinspace


1 Answers

NSUserDefaults does not support all objects, just things like NSString, NSDictionary, and most importantly, NSData. This is where the NSCoding protocol comes in handy. It allows us to transform custom objects into chunks of NSData. To do this, use the NSKeyedArchiver class to turn a custom object that conforms to NSCoding to the equivalent NSData

let obj = User()
let data = NSKeyedArchiver. archivedDataWithRootObject(obj)
/// Now you can store data

The docs can be found here and the method you are looking for is

class func archivedDataWithRootObject(_ rootObject: AnyObject) -> NSData

To get things out of NSUserDefaults, use the "inverse" class, NSKeyedUnarchiver. Its appropriate method is as follows

class func unarchiveObjectWithData(_ data: NSData) -> AnyObject?

Here is a sample.

let data = NSUserDefaults.standardUserDefaults().objectForKey("somekey")
let obj = NSKeyedUnarchiver. unarchiveObjectWithData(data) as User?

Note: I may have mixed up some optionals, but you get the jist. The documentation for NSKeyedUnarchiver can be found here.

Edit: Fixing the OP's problem was as simple as correctly using NSKeyedArchiver and subclassing NSObject.

like image 98
Brian Tracy Avatar answered Oct 05 '22 22:10

Brian Tracy