Сохранение пользовательского класса Swift с NSCoding в UserDefaults

79

В настоящее время я пытаюсь сохранить собственный класс 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. Благодарю.

Георг
источник
Здесь есть хороший учебник: ios8programminginswift.wordpress.com/2014/08/17/… Хорошее обсуждение Reddit здесь: reddit.com/r/swift/comments/28sp3z/… Еще один хороший пост здесь: myswiftjourney.me/2014/ 01.10 /… Для полного хранения объекта я рекомендую полное NSCoding - два примера ниже, второй от меня с полными структурами классов: stackoverflow.com/questions/26233067/… stackoverfl
Стив Розенберг

Ответы:

62

В 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) {
}
Гулам Расул
источник
Если я хочу хранить его постоянно, то как он должен работать. Заранее спасибо
Birju
1
Я пробовал это, но получаю сообщение об ошибке: Type Blog не соответствует протоколу Decodable
vofili
@vofili вы унаследовали класс Blog от Codable?
Гулам Расул,
как это сделать, если в моем классе блога есть словарь [String: Any]?
Али Раза,
46

Первая проблема заключается в том, что вы должны убедиться, что у вас есть неискаженное имя класса:

@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)
}
Дэвид Берри
источник
1
Благодарю. Это сделало это. NSKeyedUnarchiver был недостающим элементом. Я обновлю свой вопрос рабочим образцом. Кажется, что вы можете опустить unarc.setClass и вместо этого преобразовать unarc.decodeObjectForKey в - в данном случае - класс Blog.
Георг
1
Вам понадобится только 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 } }
CaffeineShots
21

Как предложил @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
}
Георг
источник
Что, если мой пользовательский класс имеет тип UIImage или Data? Как я могу сохранить такой класс в UserDefaults?
Нилам Пурснани
12

Вот полное решение для 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")

Вадим Булавин
источник
Это не будет хранить данные постоянно. Я хочу сохранить данные после закрытия приложения.
Birju
9

Протестировано с 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. Вы можете узнать больше об их различиях в моем ответе здесь .

Ронни Веберс
источник
Просто к сведению: причина того, что blogName является необязательным, заключается в том, что объект отражает фактические блоги, которые есть у пользователя. Имя блога автоматически выводится из тега title, когда пользователь добавляет URL-адрес нового блога. Итак, во время создания объекта у блога нет названия, и я не хотел называть новый блог «безымянным» или чем-то подобным.
Георг
7

В Swift 4 у вас есть новый протокол, который заменяет протокол NSCoding. Он называется Codableи поддерживает классы и типы Swift! (Enum, структуры):

struct CustomStruct: Codable {
    let name: String
    let isActive: Bool
}
Йерун Баккер
источник
class реализует Codable и имеет необязательный массив строк:'Attempt to insert non-property list object
Вячеслав Герчиков
Это совершенно неверный ответ. Codableникак не заменить NSCoding. Единственный способ напрямую сохранять объекты UserDefaults- сделать класс NSCodingсовместимым. Поскольку NSCodingэто a class protocol, его нельзя применять к типам struct / enum. Однако вы все равно можете сделать свою структуру / перечисление Codingсовместимой и использовать ее JSONSerializerдля кодирования Data, которое можно сохранить в UserDefaults.
Джош
4

Версия 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")
    }
}
Ник Екимов
источник
1

Я использую свою структуру. Это намного проще.

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
ЯннСтеф
источник
1

Вам нужно будет сделать класс модели истории для 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")
}

Надеюсь, это решит вашу проблему

Пареш Мангукия
источник
1

Вот простой пример сохранения настраиваемых объектов в 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)")

Вот и все.

Дхавал Х. Нена
источник
Я только что заметил, что вы отправили одинаковые ответы на два вопроса (на этот и на этот ). Если вы видите два вопроса, на которые можно ответить одним ответом, поднимите флаг дублирования. Если эти два вопроса различаются, ответьте на каждый из них. Благодаря!
Halfer
0

Я знаю, что это старый вопрос, но я написал небольшую библиотеку, которая может помочь:

Вы храните объект следующим образом:

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()
Лена Бру
источник
0

Вы можете использовать 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)
}
Умайр
источник
-3

Вы не можете напрямую сохранить объект в списке свойств; вы можете хранить только отдельные строки или другие примитивные типы (целые числа и т. д.), поэтому вам нужно хранить их как отдельные строки, например:

   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)
   }
Михаил Байнов
источник