问题
I am currently trying to save a custom Swift class to NSUserDefaults. Here is the code from my Playground:
import Foundation
class Blog : NSObject, NSCoding {
var blogName: String?
override init() {}
required init(coder aDecoder: NSCoder) {
if let blogName = aDecoder.decodeObjectForKey(\"blogName\") as? String {
self.blogName = blogName
}
}
func encodeWithCoder(aCoder: NSCoder) {
if let blogName = self.blogName {
aCoder.encodeObject(blogName, forKey: \"blogName\")
}
}
}
var blog = Blog()
blog.blogName = \"My Blog\"
let ud = NSUserDefaults.standardUserDefaults()
ud.setObject(blog, forKey: \"blog\")
When I run the code, I get the following error
Execution was interrupted, reason: signal SIGABRT.
in the last line (ud.setObject
...)
The same code also crashes when in an app with the message
\"Property list invalid for format: 200 (property lists cannot contain objects of type \'CFType\')\"
Can anybody help? I am using Xcode 6.0.1 on Maverick. Thanks.
回答1:
In Swift 4 , Use Codable.
In your case, use following code.
class Blog : Codable {
var blogName: String?
}
Now create its object. For example:
var blog = Blog()
blog.blogName = "My Blog"
Now encode it like this:
if let encoded = try? JSONEncoder().encode(blog) {
UserDefaults.standard.set(encoded, forKey: "blog")
}
and decode it like this:
if let blogData = UserDefaults.standard.data(forKey: "blog"),
let blog = try? JSONDecoder().decode(Blog.self, from: blogData) {
}
回答2:
The first problem is you have to ensure that you have a non-mangled class name:
@objc(Blog)
class Blog : NSObject, NSCoding {
Then you have to encode the object (into an NSData) before you can store it into the user defaults:
ud.setObject(NSKeyedArchiver.archivedDataWithRootObject(blog), forKey: "blog")
Similarly, to restore the object you'll need to unarchive it:
if let data = ud.objectForKey("blog") as? NSData {
let unarc = NSKeyedUnarchiver(forReadingWithData: data)
unarc.setClass(Blog.self, forClassName: "Blog")
let blog = unarc.decodeObjectForKey("root")
}
Note that if you're not using it in the playground it's a little simpler as you don't have to register the class by hand:
if let data = ud.objectForKey("blog") as? NSData {
let blog = NSKeyedUnarchiver.unarchiveObjectWithData(data)
}
回答3:
As @dan-beaulieu suggested I answer my own question:
Here is the working code now:
Note: Demangling of the class name was not necessary for the code to work in Playgrounds.
import Foundation
class Blog : NSObject, NSCoding {
var blogName: String?
override init() {}
required init(coder aDecoder: NSCoder) {
if let blogName = aDecoder.decodeObjectForKey("blogName") as? String {
self.blogName = blogName
}
}
func encodeWithCoder(aCoder: NSCoder) {
if let blogName = self.blogName {
aCoder.encodeObject(blogName, forKey: "blogName")
}
}
}
let ud = NSUserDefaults.standardUserDefaults()
var blog = Blog()
blog.blogName = "My Blog"
ud.setObject(NSKeyedArchiver.archivedDataWithRootObject(blog), forKey: "blog")
if let data = ud.objectForKey("blog") as? NSData {
let unarc = NSKeyedUnarchiver(forReadingWithData: data)
let newBlog = unarc.decodeObjectForKey("root") as Blog
}
回答4:
Tested with Swift 2.1 & Xcode 7.1.1
If you don't need blogName to be an optional (which I think you don't), I would recommend a slightly different implementation :
class Blog : NSObject, NSCoding {
var blogName: String
// designated initializer
//
// ensures you'll never create a Blog object without giving it a name
// unless you would need that for some reason?
//
// also : I would not override the init method of NSObject
init(blogName: String) {
self.blogName = blogName
super.init() // call NSObject's init method
}
func encodeWithCoder(aCoder: NSCoder) {
aCoder.encodeObject(blogName, forKey: "blogName")
}
required convenience init?(coder aDecoder: NSCoder) {
// decoding could fail, for example when no Blog was saved before calling decode
guard let unarchivedBlogName = aDecoder.decodeObjectForKey("blogName") as? String
else {
// option 1 : return an default Blog
self.init(blogName: "unnamed")
return
// option 2 : return nil, and handle the error at higher level
}
// convenience init must call the designated init
self.init(blogName: unarchivedBlogName)
}
}
test code could look like this :
let blog = Blog(blogName: "My Blog")
// save
let ud = NSUserDefaults.standardUserDefaults()
ud.setObject(NSKeyedArchiver.archivedDataWithRootObject(blog), forKey: "blog")
ud.synchronize()
// restore
guard let decodedNSData = ud.objectForKey("blog") as? NSData,
let someBlog = NSKeyedUnarchiver.unarchiveObjectWithData(decodedNSData) as? Blog
else {
print("Failed")
return
}
print("loaded blog with name : \(someBlog.blogName)")
Finally, I'd like to point out that it would be easier to use NSKeyedArchiver and save your array of custom objects to a file directly, instead of using NSUserDefaults. You can find more about their differences in my answer here.
回答5:
In Swift 4 you have a new protocol that replaces the NSCoding protocol. It's called Codable
and it supports classes and Swift types! (Enum, structs):
struct CustomStruct: Codable {
let name: String
let isActive: Bool
}
回答6:
Here is a complete solution for Swift 4 & 5.
First, implement helper methods in UserDefaults
extension:
extension UserDefaults {
func set<T: Encodable>(encodable: T, forKey key: String) {
if let data = try? JSONEncoder().encode(encodable) {
set(data, forKey: key)
}
}
func value<T: Decodable>(_ type: T.Type, forKey key: String) -> T? {
if let data = object(forKey: key) as? Data,
let value = try? JSONDecoder().decode(type, from: data) {
return value
}
return nil
}
}
Say, we want to save and load a custom object Dummy
with 2 default fields. Dummy
must conform to Codable
:
struct Dummy: Codable {
let value1 = "V1"
let value2 = "V2"
}
// Save
UserDefaults.standard.set(encodable: Dummy(), forKey: "K1")
// Load
let dummy = UserDefaults.standard.value(Dummy.self, forKey: "K1")
回答7:
Swift 3 version:
class CustomClass: NSObject, NSCoding {
let name: String
let isActive: Bool
init(name: String, isActive: Bool) {
self.name = name
self.isActive = isActive
}
// MARK: NSCoding
required convenience init?(coder decoder: NSCoder) {
guard let name = decoder.decodeObject(forKey: "name") as? String,
let isActive = decoder.decodeObject(forKey: "isActive") as? Bool
else { return nil }
self.init(name: name, isActive: isActive)
}
func encode(with coder: NSCoder) {
coder.encode(self.name, forKey: "name")
coder.encode(self.isActive, forKey: "isActive")
}
}
回答8:
I use my own struct. It's much easier.
struct UserDefaults {
private static let kUserInfo = "kUserInformation"
var UserInformation: DataUserInformation? {
get {
guard let user = NSUserDefaults.standardUserDefaults().objectForKey(UserDefaults.kUserInfo) as? DataUserInformation else {
return nil
}
return user
}
set {
NSUserDefaults.standardUserDefaults().setObject(newValue, forKey: UserDefaults.kUserInfo)
}
}
}
To use:
let userinfo = UserDefaults.UserInformation
回答9:
I know this question is Old, but I wrote a little library that might be of help:
You store the object this way:
class MyObject: NSObject, Codable {
var name:String!
var lastName:String!
enum CodingKeys: String, CodingKey {
case name
case lastName = "last_name"
}
}
self.myStoredPref = Pref<MyObject>(prefs:UserDefaults.standard,key:"MyObjectKey")
let myObject = MyObject()
//... set the object values
self.myStoredPref.set(myObject)
and then to extract the object back to its original value:
let myStoredValue: MyObject = self.myStoredPref.get()
回答10:
You will have to make History Model Class to NSCoding just like this
class SSGIFHistoryModel: NSObject, NSCoding {
var bg_image: String
var total_like: String
var total_view: String
let gifID: String
var title: String
init(bg_image: String, total_like: String, total_view: String, gifID: String, title: String) {
self.bg_image = bg_image
self.total_like = total_like
self.total_view = total_view
self.gifID = gifID
self.title = title
}
required init?(coder aDecoder: NSCoder) {
self.bg_image = aDecoder.decodeObject(forKey: "bg_image") as? String ?? ""
self.total_like = aDecoder.decodeObject(forKey: "total_like") as? String ?? ""
self.total_view = aDecoder.decodeObject(forKey: "total_view") as? String ?? ""
self.gifID = aDecoder.decodeObject(forKey: "gifID") as? String ?? ""
self.title = aDecoder.decodeObject(forKey: "title") as? String ?? ""
}
func encode(with aCoder: NSCoder) {
aCoder.encode(bg_image, forKey: "bg_image")
aCoder.encode(total_like, forKey: "total_like")
aCoder.encode(total_view, forKey: "total_view")
aCoder.encode(gifID, forKey: "gifID")
aCoder.encode(title, forKey: "title")
}
}
After then you will need to archive the object into NSData then save it to user defaults and retrieve it from user defaults and unarchive it again. You can archive and and unarchive it like this
func saveGIFHistory() {
var GIFHistoryArray: [SSGIFHistoryModel] = []
GIFHistoryArray.append(SSGIFHistoryModel(bg_image: imageData.bg_image, total_like: imageData.total_like, total_view: imageData.total_view, gifID: imageData.quote_id, title: imageData.title))
if let placesData = UserDefaults.standard.object(forKey: "GIFHistory") as? NSData {
if let placesArray = NSKeyedUnarchiver.unarchiveObject(with: placesData as Data) as? [SSGIFHistoryModel] {
for data in placesArray {
if data.gifID != imageData.quote_id {
GIFHistoryArray.append(data)
}
}
}
}
let placesData2 = NSKeyedArchiver.archivedData(withRootObject: GIFHistoryArray)
UserDefaults.standard.set(placesData2, forKey: "GIFHistory")
}
Hope this solve your problem
回答11:
A simple example of saving Custom Objects in UserDefaults would be as below :
You do not need to write the boilerplate code for saving/retrieving objects any more with the help of THE GREAT 'CODABLE', that's what it is there for you to rescue from irritating manual encoding/decoding
So just get rid of below two methods from your code if you're already using NSCoding and switch to Codable (Combination of Encodable + Decodable) protocol
required init(coder decoder: NSCoder) // NOT REQUIRED ANY MORE, DECODABLE TAKES CARE OF IT
func encode(with coder: NSCoder) // NOT REQUIRED ANY MORE, ENCODABLE TAKES CARE OF IT
Let's get started with the simplicity of Codable...
Create a custom class or struct you want to store in UserDefaults
struct Person : Codable {
var name:String
}
OR
class Person : Codable {
var name:String
init(name:String) {
self.name = name
}
}
Save object in UserDefaults as below
if let encoded = try? JSONEncoder().encode(Person(name: "Dhaval")) {
UserDefaults.standard.set(encoded, forKey: "kSavedPerson")
}
Load object from UserDefaults as below
guard let savedPersonData = UserDefaults.standard.object(forKey: "kSavedPerson") as? Data else { return }
guard let savedPerson = try? JSONDecoder().decode(Person.self, from: savedPersonData) else { return }
print("\n Saved person name : \(savedPerson.name)")
That's it ...
Happy saving / loading .. :)
回答12:
You can't store an object in the property list directly; you can only store individual Strings or other primitive types (integers etc.) So you need to store it as individual strings, such as:
override init() {
}
required public init(coder decoder: NSCoder) {
func decode(obj:AnyObject) -> AnyObject? {
return decoder.decodeObjectForKey(String(obj))
}
self.login = decode(login) as! String
self.password = decode(password) as! String
self.firstname = decode(firstname) as! String
self.surname = decode(surname) as! String
self.icon = decode(icon) as! UIImage
}
public func encodeWithCoder(coder: NSCoder) {
func encode(obj:AnyObject) {
coder.encodeObject(obj, forKey:String(obj))
}
encode(login)
encode(password)
encode(firstname)
encode(surname)
encode(icon)
}
来源:https://stackoverflow.com/questions/26469457/saving-custom-swift-class-with-nscoding-to-userdefaults