現在、カスタム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'のオブジェクトを含めることはできません)」
誰でも助けることができますか? MaverickでXcode 6.0.1を使用しています。ありがとう。
@ 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
}
最初の問題は、マングルされていないクラス名があることを確認する必要があることです。
@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)
}
Swift 4では、コード化可能を使用します。
あなたの場合、次のコードを使用します。
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) {
}
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)")
最後に、NSUserDefaultsを使用する代わりに、NSKeyedArchiverを使用して、カスタムオブジェクトの配列をファイルに直接保存する方が簡単であることを指摘したいと思います。それらの違いについては、私の答え こちら をご覧ください。
Swift 4には、NSCodingプロトコルに代わる新しいプロトコルがあります。 Codable
と呼ばれ、クラスとSwiftタイプをサポートします! (列挙型、構造体):
struct CustomStruct: Codable {
let name: String
let isActive: Bool
}
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
}
}
たとえば、2つのデフォルトフィールドを持つカスタムオブジェクト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 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
私はこの質問が古いことを知っていますが、少し書いた library それは助けになるかもしれません:
この方法でオブジェクトを保存します。
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()
このようにNSCodingにHistory Model Classを作成する必要があります
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")
}
これで問題が解決することを願っています