2014-10-20 28 views
57

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

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 nhận được lỗi "Thực thi bị gián đoạn, lý do: tín hiệu SIGABRT". ở dòng cuối cùng (ud.setObject ...)

Cùng một mã cũng đổ vỡ khi trong ứng dụng có thông báo "Danh sách sản phẩm không hợp lệ cho định dạng: 200 (danh sách thuộc tính không thể chứa đối tượng thuộc loại 'CFType')"

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

+2

bạn nên đăng mã làm việc của mình làm câu trả lời, nhận câu trả lời duy nhất ở đây là câu trả lời chỉ liên kết được coi là nguyên tắc không trả lời theo nguyên tắc ngăn xếp chồng chéo. –

+0

Có một hướng dẫn tốt ở đây: https://ios8programminginswift.wordpress.com/2014/08/17/persisting-data-with-nsuserdefaults/ Một thảo luận reddit tốt ở đây: http://www.reddit.com/r/ swift/comments/28sp3z/whats_the_best_way_to_achieve_persistence_in/Một bài đăng hay khác tại đây: http://www.myswiftjourney.me/2014/10/01/simple-persistent-storage-using-nsuserdefaults/ Để lưu trữ toàn bộ đối tượng, tôi khuyên bạn nên sử dụng NSCoding đầy đủ - hai ví dụ bên dưới, thứ hai từ tôi với cấu trúc lớp học đầy đủ: http://stackoverflow.com/questions/26233067/simple-persistent-storage-in-swift/26233274#26233274 http: // stackoverfl –

+0

mã hoạt động được đăng bên dưới. – Georg

Trả lời

16

Như @ dan-Beaulieu đề nghị tôi trả lời câu hỏi của riêng tôi:

Đây là mã đang hoạt động ngay bây giờ:

Lưu ý: Không yêu cầu xóa tên lớp để làm việc trong Sân chơ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") 
     } 
    } 

} 

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

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 đọc sai:

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

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

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

Tương tự như vậy, để khôi phục đối tượng bạn sẽ cần phải bỏ 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") 
} 

Không te rằng nếu bạn không sử dụng nó trong sân chơi đó là một chút đơn giản hơn là bạn không phải đăng ký lớp bằng tay:

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

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

+1

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

+0

biến 'blog' ở trên không phải là đối tượng tùy chỉnh trong' if let data = ud.objectForKey ("blog") là? NSData { cho phép blog = NSKeyedUnarchiver.unarchiveObjectWithData (dữ liệu) } ', trong trường hợp của tôi, tôi cần phải đưa nó vào đối tượng tùy chỉnh của tôi như thế này 'if let data = ud.objectForKey (" blog ") là? NSData { cho phép blog = NSKeyedUnarchiver.unarchiveObjectWithData (dữ liệu) nếu để thisblog = blog là? Blog { trả về thisblog // đây là đối tượng tùy chỉnh từ nsuserdefaults } } ' – CaffeineShots

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), tôi muốn giới thiệu một thực hiện hơi khác nhau :

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 mảng các đối tượng tùy chỉnh của bạn vào một tệp trực tiếp, thay vì sử dụng NSUserDefaults. Bạn có thể tìm hiểu thêm về sự khác biệt của họ trong câu trả lời của tôi here.

+0

Chỉ cần thông tin: Lý do cho blogName là một tùy chọn là đối tượng phản chiếu các blog thực tế mà người dùng có. Tên blog được tự động bắt nguồn 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 "không tên" hoặc một cái gì đó tương tự. – Georg

1

Đối với tôi tôi sử dụng struct của tôi là dễ dàng hơn

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) 
      NSUserDefaults.standardUserDefaults().synchronize() 
     } 
    } 

} 



Use : let userinfo = UserDefaults.UserInformation 
-3

Bạn không thể lưu trữ một đối tượng trong danh sách sở hữu trực tiếp; 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 phải lưu trữ nó như dây cá nhân, 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) 
    } 
3

Swift 3 phiên bản:

class CustomClass: NSObject, NSCoding { 

var name = "" 
var isActive = false 

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

}

5

Trong Swift 4 bạn có một giao thức mới thay thế Nghị định thư NSCoding . Nó được gọi là Codable và nó hỗ trợ các lớp và các loại Swift! (Enum, structs):

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

lớp thực hiện Codable và có một chuỗi tùy chọn của chuỗi: ''Cố gắng chèn đối tượng danh sách không thuộc tính' –

+0

Câu trả lời này hoàn toàn sai. 'Codable' không ** không ** thay thế' NSCoding'. Cách duy nhất để lưu trực tiếp các đối tượng vào 'UserDefaults' là làm cho lớp 'NSCoding' tuân thủ. Vì 'NSCoding' là một giao thức' lớp', nó không thể được áp dụng cho các kiểu struct/enum. Tuy nhiên, bạn vẫn có thể thực hiện cấu trúc 'Coding' của struct/enum và sử dụng' JSONSerializer' để mã hóa thành 'Data', có thể được lưu trữ trong' UserDefaults'. – Josh

Các vấn đề liên quan