为什么NSKeyedArchiver和NSKeyedUnarchiver在这里没有按预期工作?

时间:2017-02-05 11:40:22

标签: ios swift nskeyedarchiver

我一直致力于一个项目,我需要将一些对象写入磁盘,然后再将它们取回。我使用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
}

我是否误解了'在课堂上传输数据'?

2 个答案:

答案 0 :(得分: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)

答案 1 :(得分:0)

您不需要创建自己的结构,甚至不需要Archiver / Unarchiver来获取简单的字符串。可以直接从Data读取和写入String。

尝试将代码重写为这样。

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

}

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