Lưu lớp Swift tùy chỉnh với NSCoding thành UserDefaults


79

Tôi hiện đang cố gắng lưu một lớp Swift tùy chỉnh vào NSUserDefaults. Đây là mã từ Playground của tôi:

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")

Khi tôi chạy mã, tôi gặp lỗi sau

Quá trình thực thi bị gián đoạn, lý do: tín hiệu SIGABRT.

ở dòng cuối cùng ( ud.setObject...)

Mã tương tự cũng bị lỗi khi ở trong một ứng dụng có thông báo

"Danh sách thuộc tính không hợp lệ cho định dạng: 200 (danh sách thuộc tính không được chứa các đối tượng thuộc loại 'CFType')"

Ai có thể giúp đỡ? Tôi đang sử dụng Xcode 6.0.1 trên Maverick. Cảm ơn.


Có một hướng dẫn tốt ở đây: ios8programminginswift.wordpress.com/2014/08/17/… Một cuộc thảo luận về reddit hay ở đây: reddit.com/r/swift/comments/28sp3z/… Một bài hay khác ở đây: myswiftjourney.me/2014/ 01/10 / ... Đối với đối tượng đầy đủ lưu trữ tôi khuyên bạn nên toàn bộ NSCoding - hai ví dụ dưới đây, là thứ hai từ tôi với cấu trúc lớp đầy đủ: stackoverflow.com/questions/26233067/... stackoverfl
Steve Rosenberg

Câu trả lời:


61

Trong Swift 4 trở lên, hãy sử dụng Codable.

Trong trường hợp của bạn, hãy sử dụng mã sau.

class Blog: Codable {
   var blogName: String?
}

Bây giờ tạo đối tượng của nó. Ví dụ:

var blog = Blog()
blog.blogName = "My Blog"

Bây giờ hãy mã hóa nó như thế này:

if let encoded = try? JSONEncoder().encode(blog) {
    UserDefaults.standard.set(encoded, forKey: "blog")
}

và giải mã nó như thế này:

if let blogData = UserDefaults.standard.data(forKey: "blog"),
    let blog = try? JSONDecoder().decode(Blog.self, from: blogData) {
}

Nếu tôi muốn lưu trữ nó vĩnh viễn thì nó sẽ hoạt động như thế nào. Cảm ơn trước
Birju

1
Tôi đã thử điều này nhưng tôi nhận được thông báo lỗi, Loại Blog không phù hợp với giao thức Có thể giải mã
vofili

@vofili bạn có kế thừa lớp Blog từ Codable không?
Ghulam Rasool

làm thế nào để làm điều đó nếu tôi có một từ điển [Chuỗi: Bất kỳ] trong lớp Blog của mình?
Ali Raza

46

Vấn đề đầu tiên là bạn phải đảm bảo rằng bạn có một tên lớp không bị xáo trộn:

@objc(Blog)
class Blog : NSObject, NSCoding {

Sau đó, bạn phải mã hóa đối tượng (thành NSData) trước khi bạn có thể lưu trữ nó vào mặc định của người dùng:

ud.setObject(NSKeyedArchiver.archivedDataWithRootObject(blog), forKey: "blog")

Tương tự, để khôi phục đối tượng, bạn sẽ cần hủy lưu trữ nó:

if let data = ud.objectForKey("blog") as? NSData {
    let unarc = NSKeyedUnarchiver(forReadingWithData: data)
    unarc.setClass(Blog.self, forClassName: "Blog")
    let blog = unarc.decodeObjectForKey("root")
}

Lưu ý rằng nếu bạn không sử dụng nó trong sân chơi, nó sẽ đơn giản hơn một chút vì bạn không phải đăng ký lớp học bằng tay:

if let data = ud.objectForKey("blog") as? NSData {
    let blog = NSKeyedUnarchiver.unarchiveObjectWithData(data)
}

1
Cảm ơn. Điều đó đã làm được. NSKeyedUnarchiver là mảnh ghép còn thiếu. Tôi sẽ cập nhật câu hỏi của mình với một mẫu làm việc. Có vẻ như bạn có thể bỏ qua unarc.setClass và thay vào đó là unarc.decodeObjectForKey - trong trường hợp này - là lớp Blog.
Georg

1
Bạn chỉ cần setClassnếu bạn đang làm việc trong Playground, vì nhiều lý do khác nhau mà các lớp học không được đăng ký tự động ở đó.
David Berry

các blogbiến trên không phải là đối tượng tùy chỉnh trong if let data = ud.objectForKey("blog") as? NSData { let blog = NSKeyedUnarchiver.unarchiveObjectWithData(data) }, trong trường hợp của tôi, tôi cần phải bỏ nó vào đối tượng tùy chỉnh của tôi như thế nàyif 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

Như @ dan-beaulieu gợi ý, tôi trả lời câu hỏi của riêng mình:

Đây là mã làm việc bây giờ:

Lưu ý: Không cần gỡ bỏ tên lớp để mã hoạt động trong 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
}

Điều gì sẽ xảy ra nếu lớp tùy chỉnh của tôi có loại UIImage hoặc Data? Làm cách nào để lưu trữ loại lớp đó trong UserDefaults?
Neelam Pursnani

12

Đây là một giải pháp hoàn chỉnh cho Swift 4 & 5 .

Đầu tiên, hãy triển khai các phương thức trợ giúp trong UserDefaultstiện ích mở rộng:

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
    }
}

Giả sử, chúng tôi muốn lưu và tải một đối tượng tùy chỉnh Dummyvới 2 trường mặc định. Dummyphải phù hợp với 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")


Điều này sẽ không lưu trữ vĩnh viễn dữ liệu. Tôi muốn lưu trữ dữ liệu sau khi ứng dụng bị hủy.
Birju

9

Đã thử nghiệm với Swift 2.1 & Xcode 7.1.1

Nếu bạn không cần blogName là một tùy chọn (mà tôi nghĩ bạn không cần), tôi sẽ đề xuất một cách triển khai hơi khác:

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)
    }
}

mã kiểm tra có thể trông như thế này:

    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)")

Cuối cùng, tôi muốn chỉ ra rằng sẽ dễ dàng hơn khi sử dụng NSKeyedArchiver và lưu trực tiếp mảng đối tượng tùy chỉnh của bạn vào một tệp, thay vì sử dụng NSUserDefaults. Bạn có thể tìm thêm về sự khác biệt của chúng trong câu trả lời của tôi tại đây .


Just FYI: Lý do blogName là tùy chọn là đối tượng phản ánh các blog thực tế mà người dùng có. Tên blog được lấy tự động từ thẻ tiêu đề khi người dùng thêm URL cho blog mới. Vì vậy, tại thời điểm tạo đối tượng, không có tên blog và tôi muốn tránh gọi một blog mới là "không tên" hoặc một cái gì đó tương tự.
Georg

7

Trong Swift 4, bạn có một giao thức mới thay thế giao thức NSCoding. Nó được gọi Codablevà nó hỗ trợ các lớp và kiểu Swift! (Enum, cấu trúc):

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

dụng cụ lớp Codable và có một mảng tùy chọn của chuỗi:'Attempt to insert non-property list object
Vyachaslav Gerchicov

Câu trả lời này hoàn toàn sai. Codablekhông không thay thế NSCoding. Cách duy nhất để lưu trực tiếp các đối tượng vào UserDefaultslà làm cho lớp NSCodingtuân thủ. Vì NSCodinglà a class protocolnên nó không thể được áp dụng cho các kiểu struct / enum. Tuy nhiên, bạn vẫn có thể Codingtuân thủ struct / enum của mình và sử dụng JSONSerializerđể mã hóa Data, có thể được lưu trữ trong UserDefaults.
Josh

4

Phiên bản 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

Tôi sử dụng cấu trúc của riêng tôi. Nó dễ dàng hơn nhiều.

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)
        }
    }
}

Để sử dụng:

let userinfo = UserDefaults.UserInformation

1

Bạn sẽ phải chuyển Lớp mô hình lịch sử thành NSCoding giống như thế này

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")
}
}

Sau đó, bạn sẽ cần lưu trữ đối tượng vào NSData, sau đó lưu nó về mặc định của người dùng và truy xuất nó từ mặc định của người dùng và hủy lưu trữ lại. Bạn có thể lưu trữ và hủy lưu trữ nó như thế này

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")
}

Hy vọng điều này giải quyết vấn đề của bạn


1

Một ví dụ đơn giản về việc lưu Đối tượng tùy chỉnh trong UserDefaults sẽ như sau:

Bạn không cần phải viết mã soạn sẵn để lưu / truy xuất các đối tượng nữa với sự trợ giúp của 'GIẢI MÃ' TUYỆT VỜI, đó là những gì có sẵn để bạn giải cứu khỏi mã hóa / giải mã thủ công khó chịu.

Vì vậy, chỉ cần loại bỏ hai phương pháp dưới đây khỏi mã của bạn nếu bạn đã sử dụng NSCoding và chuyển sang giao thức Codable (Kết hợp có thể mã hóa + có thể giải mã)

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

Hãy bắt đầu với sự đơn giản của Codable:

Tạo một lớp hoặc cấu trúc tùy chỉnh mà bạn muốn lưu trữ trong UserDefaults

struct Person : Codable {
    var name:String
}

OR

class Person : Codable {

    var name:String

    init(name:String) {
        self.name = name
    }
}

Lưu đối tượng trong UserDefaults như bên dưới

 if let encoded = try? JSONEncoder().encode(Person(name: "Dhaval")) {
     UserDefaults.standard.set(encoded, forKey: "kSavedPerson")
 }

Tải đối tượng từ UserDefaults như bên dưới

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)")

Đó là nó.


Tôi vừa phát hiện ra rằng bạn đã đăng các câu trả lời giống hệt nhau cho hai câu hỏi (câu hỏi này và câu hỏi này ). Nếu bạn thấy hai câu hỏi có thể được trả lời bằng một câu trả lời, thì vui lòng giơ cờ trùng lặp. Nếu hai câu hỏi khác nhau, vui lòng điều chỉnh câu trả lời của bạn cho từng câu hỏi. Cảm ơn!
halfer

0

Tôi biết câu hỏi này là Cũ, nhưng tôi đã viết một thư viện nhỏ có thể hữu ích:

Bạn lưu trữ đối tượng theo cách này:

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)

và sau đó để giải nén đối tượng trở lại giá trị ban đầu của nó:

let myStoredValue: MyObject = self.myStoredPref.get()

0

Bạn có thể sử dụng PropertyListEncoder để lưu các đối tượng tùy chỉnh của mình vào UserDefaults, sau khi bạn đã triển khai giao thức Codable trên lớp của mình. Cho phép lớp tùy chỉnh là Người dùng

class User: NSObject, Codable {

  var id: Int?
  var name: String?
  var address: String?
}

Codable ngụ ý rằng nó thực hiện cả hai giao thức có thể giải mã và mã hóa. Như tên cho thấy, bằng cách triển khai Có thể mã hóa và Có thể giải mã, bạn cung cấp cho lớp của mình khả năng được mã hóa và giải mã tới và từ một đại diện bên ngoài, ví dụ như JSON hoặc Danh sách thuộc tính.

Bây giờ bạn có thể lưu mô hình của mình được dịch vào danh sách thuộc tính.

if let encodedData = try? PropertyListEncoder().encode(userModel){
  UserDefaults.standard.set(encodedData, forKey:"YOUR_KEY")
  UserDefaults.standard.synchronize();
}

Và bạn có thể truy xuất mô hình của mình bằng cách sử dụng PropertyListDecoder. Bởi vì bạn đã mã hóa nó dưới dạng danh sách tài sản.

if let data = UserDefaults.standard.value(forKey:"YOUR_KEY") as? Data {
     return try? PropertyListDecoder().decode(User.self, from:data)
}

-3

Bạn không thể lưu trữ trực tiếp một đối tượng trong danh sách thuộc tính; bạn chỉ có thể lưu trữ các Chuỗi riêng lẻ hoặc các kiểu nguyên thủy khác (số nguyên, v.v.) Vì vậy, bạn cần lưu trữ nó dưới dạng các chuỗi riêng lẻ, chẳng hạn như:

   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)
   }
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.