【问题标题】:Why aren't NSKeyedArchiver and NSKeyedUnarchiver working as expected here?为什么 NSKeyedArchiver 和 NSKeyedUnarchiver 在这里没有按预期工作?
【发布时间】:2017-02-05 11:40:22
【问题描述】:

我一直在做一个项目,我需要将一些对象写入磁盘,然后稍后再将它们取回。我使用NSKeyedArchiverNSKeyedUnarchiver 进行写入和读取,确保我的自定义类符合NSCoding。它没有工作,因此,经过很长时间尝试调试它并搜索网络后,我决定隔离问题。

有人能告诉我为什么这段代码没有按照我的预期做吗?这可能是显而易见的,但盯着它看了好几个小时后,我仍然无法解决。

我在Main.storyboard 中定义了一个视图控制器,其中包含UILabelUITextFieldUIButton。这个想法是用户可以输入一些内容,按下“发布”按钮,标签将显示他们输入的内容。每次用户输入内容时,我都希望将其写入磁盘,以便在退出应用程序并再次加载视图时,将返回消息并通过标签显示。

目前,标签显示用户输入的内容,但如果我退出应用并重新打开它,标签只会显示“您在此处的消息”,这是我在情节提要中分配给它的字符串。

我感觉这可能与unarchiveObject(withFile:) 调用和/或对String 的类型转换有关,因为我在某处读到,从 Swift 3 开始,值类型必须使用它们自己的方法(例如unarchiveBool(withFile:)),尽管String 似乎没有这种方法。

import UIKit

class ViewController: UIViewController {

    @IBOutlet weak var outputLabel: UILabel!
    @IBOutlet weak var textBox: UITextField!

    var filePath: URL!

    override func viewDidLoad() {
        super.viewDidLoad()

        filePath = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0].appendingPathComponent("message")

        // Load message
        if let loadedMessage = NSKeyedUnarchiver.unarchiveObject(withFile: filePath.absoluteString) as? String {
            outputLabel.text = loadedMessage
        }
    }

    @IBAction func textFieldDoneEditing(_ sender: UITextField) {
        sender.resignFirstResponder()
    }

    @IBAction func postButtonTapped(_ sender: Any) {
        outputLabel.text = textBox.text
        textBox.text = ""

        let str = outputLabel.text!

        // Save message      
        let data = NSKeyedArchiver.archivedData(withRootObject: str)
        do {
            try data.write(to: filePath)
        } catch {
            print("Couldn't write file.")
        }
    }
}

好的,@gkchristopher 指出NSCoder 不能直接与String 一起工作,所以我应该将我的数据包装在一个符合NSCoding 的类中。所以这是一个尝试这样做的小类:

class DataThing: NSObject, NSCoding {
    var message: String!

    init(withMessage message: String) {
        self.message = message
    }

    required convenience init?(coder aDecoder: NSCoder) {
        self.init(withMessage: aDecoder.decodeObject(forKey: "message") as! String)
    }

    func encode(with aCoder: NSCoder) {
        aCoder.encode(message, forKey: "message")
    }
}

我可以用这个类很好地写数据:

let dataThing = DataThing(withMessage: str)

        // Save message
        let data = NSKeyedArchiver.archivedData(withRootObject: dataThing)
        do {
            try data.write(to: filePath)
        } catch {
            print("couldn't write file.")
        }

但是,阅读仍然不起作用:

if let loadedData = NSKeyedUnarchiver.unarchiveObject(withFile: filePath.absoluteString) as? DataThing {
            outputLabel.text = loadedData.message
}

我是否误解了“将您的数据包装在一个类中”?

【问题讨论】:

  • 你有调试过吗?
  • 是的。它似乎成功地写出了数据(没有抛出异常),但是当我重建并运行应用程序时,loadedMessage 字符串在局部变量查看器中旁边写着“无法读取数据”。基于此,我认为要么是写入数据的方式有问题(导致以后无法读取),要么只是读取有问题。
  • 我现在将filePath 设置为URL(fileURLWithPath: "/Users/Xander/Desktop") .appendingPathComponent("message"),看看如果我尝试直接写到我的桌面会发生什么。它可以工作,并且该文件似乎包含数据。但是仍然无法取消存档...
  • 您实际上并不需要创建自己的数据类型。直接读写数据即可。

标签: ios swift nskeyedarchiver


【解决方案1】:

更新答案:

filePath.absoluteString 更改为filePath.path

原答案:

NSCoder 可能无法直接与 Swift 结构一起使用,例如 String。您需要将数据包装在一个类中并符合NSCoding 才能使用NSKeyedArchiver

例子:

let file = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0].appendingPathComponent("message")
let dataThing = DataThing(withMessage: "Boo")
NSKeyedArchiver.archiveRootObject(dataThing, toFile: file.path)

let retrieved = NSKeyedUnarchiver.unarchiveObject(withFile: file.path)

dump(retrieved)

【讨论】:

  • 该类在 cmets 中非常难以阅读,但我不确定在哪里发布此添加...
  • @xander 您可以编辑您的问题以添加更新信息。
  • @Abizern 我知道,我只是认为这样做没有意义......不过我现在会的。
  • 另外,您应该尝试使用filePath.path 而不是filePath.absoluteString。在这种情况下,这两者是不同的。
  • 是的!这就是我刚刚所做的,现在它可以工作了。尽管进行此更改后,我不再需要使用 DataThing 类 — 我可以直接存档字符串。
【解决方案2】:

您不需要创建自己的结构,甚至不需要归档器/取消归档器来处理简单的字符串。可以直接从数据中读取和写入字符串。

尝试重写您的代码,使其看起来像这样。

class ViewController: UIViewController {

    @IBOutlet var label: UILabel!
    @IBOutlet var textField: UITextField!

    lazy var fileURL: URL = {
        do {
            var url = try FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
            url.appendPathComponent("message")
            return url
        } catch {
            fatalError("Cannot create storage file")
        }
    }()


    override func viewDidLoad() {
        super.viewDidLoad()
        do {
            let message = try String(contentsOf: fileURL)
            label.text = message

        } catch {
            label.text = "No message available"
        }
    }

    @IBAction func post(_ sender: UIButton) {
        label.text = textField.text

        guard
            let text = textField.text,
            let data = text.data(using: .utf8),
            text != ""
            else { return }

        do {
            try data.write(to: fileURL)
        } catch {
            print(error)
        }

        textField.text = ""
    }

}

我对这些错误有点快和松散,您可以根据您的应用程序来处理它们。

【讨论】:

  • 我是这么认为的。所有子对象都符合NSCoding 是有意义的,而不仅仅是根对象。在这种情况下,将不符合要求的类型 (String) 包装在符合要求的类型 (DataThing) 中似乎无济于事。
  • 对于简单的结构,您可以只创建一个字典表示并读取和写入 plist。对于更复杂的事情,还有其他可能更合适的持久性形式(Core Data、Realm、Mantle 等)
猜你喜欢
  • 2022-08-02
  • 2018-07-20
  • 1970-01-01
  • 2022-10-14
  • 1970-01-01
  • 2020-07-24
  • 2011-11-20
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多