Сохранение пользовательского класса Swift с NSCoding в UserDefaults
в настоящее время я пытаюсь сохранить пользовательский класс Swift в NSUserDefaults. Вот код с моей игровой площадки:
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")
когда я запускаю код, я получаю следующую ошибку
исполнение было прервано, причина: сигнал SIGABRT.
в последней строке (ud.setObject
...)
тот же код также аварийно завершает работу, когда в приложении с сообщением
" недопустимый список свойств для формата: 200 (списки свойств не могут содержать объекты типа "CFType")"
может кто-нибудь помочь? Я использую Xcode 6.0.1 на Maverick. Спасибо.
8 ответов
первая проблема заключается в том, что вы должны убедиться, что у вас есть не искаженное имя класса:
@objc(Blog)
class Blog : NSObject, NSCoding {
затем вам нужно кодировать объект (в NSData), прежде чем вы сможете сохранить его в пользовательские значения по умолчанию:
ud.setObject(NSKeyedArchiver.archivedDataWithRootObject(blog), forKey: "blog")
аналогично, чтобы восстановить объект, вам нужно будет его разархивировать:
if let data = ud.objectForKey("blog") as? NSData {
let unarc = NSKeyedUnarchiver(forReadingWithData: data)
unarc.setClass(Blog.self, forClassName: "Blog")
let blog = unarc.decodeObjectForKey("root")
}
обратите внимание, что если вы не используете его на игровой площадке, это немного проще, так как вам не нужно регистрировать класс вручную:
if let data = ud.objectForKey("blog") as? NSData {
let blog = NSKeyedUnarchiver.unarchiveObjectWithData(data)
}
как @ dan-beaulieu предложил мне ответить на мой собственный вопрос:
вот рабочий код:
Примечание: Demangling имени класса не является необходимым для кода для работы в детских площадок.
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
}
протестировано с Swift 2.1 & Xcode 7.1.1
Если вам не нужно, чтобы blogName был необязательным (что, я думаю, вам не нужно), я бы рекомендовал немного другую реализацию:
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)
}
}
тестовый код может выглядеть так :
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)")
наконец, я хотел бы отметить, что было бы проще использовать NSKeyedArchiver и сохранить массив пользовательских объектов в файл напрямую, вместо использования NSUserDefaults. Вы можете найти больше о их различия в моем ответе здесь.
в Swift 4 у вас есть новый протокол, который заменяет протокол NSCoding. Это называется Codable
и он поддерживает классы и типы Swift! (Перечисление, структуры):
struct CustomStruct: Codable {
let name: String
let isActive: Bool
}
В Swift 4 Используйте Codable.
в вашем случае, используйте следующий код.
struct Blog : NSObject, Codable {
var blogName: String?
}
Теперь создайте свой объект. Например:
var blog = Blog()
blog.blogName = "My Blog"
теперь Закодируйте его так:
if let encoded = try? JSONEncoder().encode(blog) {
UserDefaults.standard.set(encoded, forKey: "blog")
}
и расшифруйте его так:
if let blogData = UserDefaults.standard.data(forKey: "blog"),
let blog = try? JSONDecoder().decode(Blog.self, from: blogData) {
}
Swift 3 версия:
class CustomClass: NSObject, NSCoding {
var name = ""
var isActive = false
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")
}
}
для меня я использую мою структуру проще
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)
NSUserDefaults.standardUserDefaults().synchronize()
}
}
}
Use : let userinfo = UserDefaults.UserInformation
вы не можете хранить объект в списке свойств напрямую; вы можете хранить только отдельные строки или другие примитивные типы (целые числа и т. д.) Поэтому вам нужно сохранить его как отдельные строки, такие как:
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)
}