В настоящее время я пытаюсь сохранить собственный класс 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. Благодарю.
Ответы:
В Swift 4 или выше используйте Codable.
В вашем случае используйте следующий код.
class Blog: 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) { }
источник
Первая проблема заключается в том, что вы должны убедиться, что у вас есть неискаженное имя класса:
@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) }
источник
setClass
если вы работаете на игровой площадке, по разным причинам классы не регистрируются там автоматически.blog
выше переменная не пользовательский объектif let data = ud.objectForKey("blog") as? NSData { let blog = NSKeyedUnarchiver.unarchiveObjectWithData(data) }
, в моем случае , мне нужно , чтобы бросить его в свой пользовательский объект , как этоif let data = ud.objectForKey("blog") as? NSData { let blog = NSKeyedUnarchiver.unarchiveObjectWithData(data) if let thisblog = blog as? Blog { return thisblog //this is the custom object from nsuserdefaults } }
Как предложил @dan-beaulieu, я отвечу на свой вопрос:
Вот теперь рабочий код:
Примечание. Разборчивание имени класса не требовалось для работы кода в 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 }
источник
Вот полное решение для Swift 4 и 5 .
Сначала реализуйте вспомогательные методы в
UserDefaults
расширении: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 } }
Скажем, мы хотим сохранить и загрузить настраиваемый объект
Dummy
с двумя полями по умолчанию.Dummy
должен соответствовать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")
источник
Протестировано с 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! (Enum, структуры):struct CustomStruct: Codable { let name: String let isActive: Bool }
источник
'Attempt to insert non-property list object
Codable
никак не заменитьNSCoding
. Единственный способ напрямую сохранять объектыUserDefaults
- сделать классNSCoding
совместимым. ПосколькуNSCoding
это aclass protocol
, его нельзя применять к типам struct / enum. Однако вы все равно можете сделать свою структуру / перечислениеCoding
совместимой и использовать ееJSONSerializer
для кодированияData
, которое можно сохранить вUserDefaults
.Версия Swift 3:
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") } }
источник
Я использую свою структуру. Это намного проще.
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) } } }
Использовать:
let userinfo = UserDefaults.UserInformation
источник
Вам нужно будет сделать класс модели истории для NSCoding, как это
Swift 4 и 5.
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") } }
После этого вам нужно будет заархивировать объект в NSData, затем сохранить его с пользовательскими настройками по умолчанию, извлечь его из пользовательских настроек по умолчанию и снова разархивировать. Вы можете заархивировать и разархивировать его вот так
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") }
источник
Вот простой пример сохранения настраиваемых объектов в UserDefaults:
Вам больше не нужно писать шаблонный код для сохранения / извлечения объектов с помощью БОЛЬШОГО «CODABLE», это то, что вы можете спасти от раздражающего ручного кодирования / декодирования.
Так что просто избавьтесь от двух следующих методов из своего кода, если вы уже используете NSCoding, и переключитесь на протокол Codable (комбинация Encodable + Decodable).
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
Начнем с простоты Codable:
Создайте собственный класс или структуру, которую хотите сохранить в UserDefaults
struct Person : Codable { var name:String } OR class Person : Codable { var name:String init(name:String) { self.name = name } }
Сохраните объект в UserDefaults, как показано ниже
if let encoded = try? JSONEncoder().encode(Person(name: "Dhaval")) { UserDefaults.standard.set(encoded, forKey: "kSavedPerson") }
Загрузите объект из UserDefaults, как показано ниже
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)")
Вот и все.
источник
Я знаю, что это старый вопрос, но я написал небольшую библиотеку, которая может помочь:
Вы храните объект следующим образом:
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)
а затем извлечь объект обратно в исходное значение:
let myStoredValue: MyObject = self.myStoredPref.get()
источник
Вы можете использовать PropertyListEncoder для сохранения ваших пользовательских объектов в UserDefaults после того, как вы реализовали протокол Codable в своем классе. Пусть пользовательский класс будет User
class User: NSObject, Codable { var id: Int? var name: String? var address: String? }
Codable подразумевает, что он реализует протоколы Decodable и Encodable. Как следует из названия, реализуя Encodable и Decodable, вы даете своему классу возможность кодироваться и декодироваться во внешнее представление и обратно, например JSON или Property List.
Теперь вы можете сохранить преобразованную модель в список свойств.
if let encodedData = try? PropertyListEncoder().encode(userModel){ UserDefaults.standard.set(encodedData, forKey:"YOUR_KEY") UserDefaults.standard.synchronize(); }
И вы можете получить свою модель с помощью PropertyListDecoder. Потому что вы закодировали его как список свойств.
if let data = UserDefaults.standard.value(forKey:"YOUR_KEY") as? Data { return try? PropertyListDecoder().decode(User.self, from:data) }
источник
Вы не можете напрямую сохранить объект в списке свойств; вы можете хранить только отдельные строки или другие примитивные типы (целые числа и т. д.), поэтому вам нужно хранить их как отдельные строки, например:
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) }
источник