如何在Swift中归档和取消归档自定义对象?或者如何在Swift中将自定义对象保存到NSUserDefaults?

时间:2014-07-09 16:58:13

标签: ios cocoa-touch swift ios8

我有一个班级

class Player {

    var name = ""

    func encodeWithCoder(encoder: NSCoder) {
        encoder.encodeObject(name)
    }

    func initWithCoder(decoder: NSCoder) -> Player {
        self.name = decoder.decodeObjectForKey("name") as String
        return self
    }

    init(coder aDecoder: NSCoder!) {
        self.name = aDecoder.decodeObjectForKey("name") as String
    }

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

我希望将其序列化并保存到用户默认值。

首先,我不确定如何正确编写编码器和解码器。所以对于init我写了两个方法。

当我尝试执行此代码时:

func saveUserData() {
    let player1 = Player(name: "player1")
    let myEncodedObject = NSKeyedArchiver.archivedDataWithRootObject(player1)
    NSUserDefaults.standardUserDefaults().setObject(myEncodedObject, forKey: "player")
}

app崩溃,我收到此消息:

*** NSForwarding: warning: object 0xebf0000 of class '_TtC6GameOn6Player' does not implement methodSignatureForSelector: -- trouble ahead

我做错了什么?

5 个答案:

答案 0 :(得分:11)

NSKeyedArchiver仅适用于Objective-C类,而不适用于纯Swift类。您可以通过使用@objc属性标记类或通过继承Objective-C类(例如NSObject)来将类桥接到Objective-C。

有关详细信息,请参阅Using Swift with Cocoa and Objective-C

答案 1 :(得分:11)

使用XCode 7.1.1测试, Swift 2.1 & iOS 9

您可以选择保存自定义对象(数组):

  • NSUserDefaults :存储应用设置,偏好设置,用户默认值: - )
  • NSKeyedArchiver :用于常规数据存储
  • 核心数据:用于更复杂的数据存储(类似数据库)

我将Core数据排除在讨论之外,但是想要告诉你为什么你应该更好地使用NSKeyedArchiver而不是NSUserdefaults。

我已更新您的Player类并为这两个选项提供了方法。虽然两种选择都有效,但如果比较'load& amp;保存'方法你会看到 NSKeydArchiver需要更少的代码来处理自定义对象数组。使用NSKeyedArchiver,您可以轻松地将内容存储到单独的文件中,而不必担心每个属性的唯一“键”名称。

import UIKit

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        // create some data
        let player1 = Player(name: "John")
        let player2 = Player(name: "Patrick")
        let playersArray = [player1, player2]

        print("--- NSUserDefaults demo ---")
        Player.savePlayersToUserDefaults(playersArray)
        if let retreivedPlayers = Player.loadPlayersFromUserDefaults() {
            print("loaded \(retreivedPlayers.count) players from NSUserDefaults")
            print("\(retreivedPlayers[0].name)")
            print("\(retreivedPlayers[1].name)")
        } else {
            print("failed")
        }

        print("--- file demo ---")
        Player.savePlayersToDisk(playersArray)
        if let retreivedPlayers = Player.loadPlayersFromDisk() {
            print("loaded \(retreivedPlayers.count) players from disk")
            print("\(retreivedPlayers[0].name)")
            print("\(retreivedPlayers[1].name)")
        } else {
            print("failed")
        }
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
}

我已按如下方式测试此类(单视图应用程序,在ViewController的viewDidLoad方法中)

true

如上所述,两种方法都产生相同的结果

同样在现实生活中,你可以做更好的错误处理,作为存档& unarchiving可能会失败。

答案 2 :(得分:9)

在Swift 4中,你不再需要NSCoding了!有一个名为Codable的新协议!

class Player: Codable {

    var name = ""

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

Codable还支持Enums和Structs,因此您可以根据需要将播放器类重写为结构体!

struct Player: Codable {
    let name: String
}

要在Userdefaults中保存播放器:

let player = Player(name: "PlayerOne")
try? UserDefaults.standard.set(PropertyListEncoder().encode(player), forKey: "player")

注意:PropertyListEncoder()是框架Foundation

中的一个类

要检索:

let encoded = UserDefault.standard.object(forKey: "player") as! Data
let storedPlayer = try! PropertyListDecoder().decode(Player.self, from: encoded)

有关详情,请参阅https://developer.apple.com/documentation/swift/codable

答案 3 :(得分:4)

  

我有一个班级

    class Player {
        var name = ""
        init(name: String) {
            self.name = name
        }
    }
     

我希望将其序列化并保存为用户默认值。

在Swift 4 / iOS 11中,有一种全新的方法可以做到这一点。它的优点是任何 Swift对象都可以使用它 - 不仅仅是类,还有结构和枚举。

您会注意到我已经省略了与NSCoding相关的方法,因为您不需要它们用于此目的。如你所知,你可以在这里采用NSCoding;但你不必。 (并且结构或枚举根本不能采用NSCoding。)

首先声明您的课程采用Codable协议:

class Player : Codable {
    var name = ""
    init(name: String) {
        self.name = name
    }
}

然后将它序列化为可以存储在UserDefaults中的Data对象(NSData)变得很简单。最简单的方法是使用属性列表作为中介:

let player = Player(name:"matt")
try? UserDefaults.standard.set(PropertyListEncoder().encode(player), 
    forKey:"player")

如果您使用这种方法,现在让我们证明您可以从UserDefaults中取出同一个播放器:

if let data = UserDefaults.standard.object(forKey:"player") as? Data {
    if let p = try? PropertyListDecoder().decode(Player.self, from: data) {
        print(p.name) // "matt"
    }
}

如果你宁愿通过NSKeyedArchiver / NSKeyedUnarchiver,你也可以这样做。实际上,在某些情况下您必须这样做:您将获得一个NSCoder,并且您需要在其中编码您的Codable对象。在最近的测试中,Xcode 9也引入了一种方法。例如,如果您正在编码,则将NSCoder转换为NSKeyedArchiver并调用encodeEncodable

答案 4 :(得分:4)

使用可编码新的ios 11协议,您现在可以让您的类使用 JSONEncoder JSONDecoder <来实现它并归档/取消归档它的对象/ p>

struct Language: Codable {
    var name: String
    var version: Int
}

let swift = Language(name: "Swift", version: 4)
let encoder = JSONEncoder()
if let encoded = try? encoder.encode(swift) {
    // save `encoded` somewhere
}

if let encoded = try? encoder.encode(swift) {
if let json = String(data: encoded, encoding: .utf8) {
    print(json)
}

let decoder = JSONDecoder()
if let decoded = try? decoder.decode(Language.self, from: encoded) {
    print(decoded.name)
}