Сохранение пользовательского класса 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) }