Збереження власного класу Swift із кодуванням NSC до 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/ 10/01 /… Для повноцінного зберігання об’єктів я рекомендую повне кодування NSC - два приклади нижче, другий - від мене із повними структурами класів: 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) {
}

Якщо я хочу зберегти його назавжди, тоді як це повинно працювати. Заздалегідь
дякую

1
Я спробував це, але, я отримую повідомлення про помилку, Тип блогу не відповідає протоколу Розшифровка
vofili

@vofili ти успадкував клас Blog від Codable?
Ghulam Rasool

як це зробити, якщо я маю словник [рядок: будь-який] у своєму класі блогу?
Алі Раза,

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?
Neelam Pursnani

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із 2 полями за замовчуванням. 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")


Це не буде зберігати дані назавжди. Я хочу зберігати дані після вбиття програми.
Бірджу

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. Більше про їхні відмінності ви можете знайти в моїй відповіді тут .


Просто FYI: Причиною того, що blogName є необов’язковим, є те, що об’єкт відображає фактичні блоги, які має користувач. Назва блогу автоматично отримується з тегу заголовка, як тільки користувач додає URL-адресу нового блогу. Отже, під час створення об’єкта немає імені блогу, і я хотів уникати називати новий блог "неназваним" чи чимось подібним.
Георг

7

У Swift 4 у вас є новий протокол, який замінює протокол NSCoding. Він називається, Codableі він підтримує класи та Swift типи! (Enum, структури):

struct CustomStruct: Codable {
    let name: String
    let isActive: Bool
}

клас реалізує Codable і має необов’язковий масив рядків:'Attempt to insert non-property list object
Вячаслав Герчиков

Ця відповідь абсолютно помилкова. Codableніяк НЕ замінити NSCoding. Єдиний спосіб безпосереднього збереження об’єктів - UserDefaultsце зробити клас NSCodingсумісним. Оскільки NSCodingє a class protocol, його не можна застосовувати до типів struct / enum. Тим не менш, ви все ще можете зробити свою 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 саме так

Свіфт 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)")

Це воно.


Я щойно помітив, що ви опублікували однакові відповіді на два запитання ( це і це ). Якщо ви бачите два запитання, на які можна відповісти однією відповіддю, підніміть прапор, що повторюється. Якщо два питання відрізняються, будь ласка, адаптуйте свою відповідь на кожне питання. Дякую!
половинка

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 або списку властивостей.

Тепер ви можете зберегти свою модель, перекладену в список властивостей.

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)
   }
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.