为什么 NSKeyedArchiver 和 NSKeyedUnarchiver 在这里没有按预期工作?
Why aren't NSKeyedArchiver and NSKeyedUnarchiver working as expected here?
我一直在做一个项目,我需要将一些对象写入磁盘,然后再将它们取回。我使用 NSKeyedArchiver
和 NSKeyedUnarchiver
进行写作和阅读,确保我的自定义 classes 符合 NSCoding
。它不工作,因此,在尝试调试它并搜索 Web 很长时间后,我决定找出问题所在。
有人能告诉我为什么这段代码不符合我的预期吗?这可能很明显,但在盯着它看了好几个小时后,我还是想不通。
我在 Main.storyboard
中定义了一个视图控制器,其中包含一个 UILabel
、一个 UITextField
和一个 UIButton
。这个想法是用户可以输入一些东西,按下 'Post' 按钮,标签将显示他们输入的内容。每次用户输入内容时,我希望将其写入磁盘,以便在应用程序退出并再次加载视图时,消息被带回并由标签显示。
目前,标签会显示用户输入的内容,但如果我退出应用程序并重新打开它,标签只会显示 'Your message here',这是我在故事板中分配给它的字符串。
我觉得这可能与 unarchiveObject(withFile:)
调用 and/or 到 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 中.所以这里有一个小的 class 试图做到这一点:
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")
}
}
我可以用这个 class:
写数据
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
}
我是不是误解了'Wrap your data in a class'?
更新后的答案:
将 filePath.absoluteString
更改为 filePath.path
。
原回答:
NSCoder
可能无法直接与 Swift 结构一起使用,例如 String
。您需要将数据包装在 class 中并符合 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)
您不需要创建自己的结构,您甚至不需要 Archiver/Unarchiver 一个简单的字符串。可以直接从数据读取和写入字符串。
尝试重写您的代码,使其看起来像这样。
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 = ""
}
}
我对错误的反应有点快,你可以根据你的应用程序来处理它们。
我一直在做一个项目,我需要将一些对象写入磁盘,然后再将它们取回。我使用 NSKeyedArchiver
和 NSKeyedUnarchiver
进行写作和阅读,确保我的自定义 classes 符合 NSCoding
。它不工作,因此,在尝试调试它并搜索 Web 很长时间后,我决定找出问题所在。
有人能告诉我为什么这段代码不符合我的预期吗?这可能很明显,但在盯着它看了好几个小时后,我还是想不通。
我在 Main.storyboard
中定义了一个视图控制器,其中包含一个 UILabel
、一个 UITextField
和一个 UIButton
。这个想法是用户可以输入一些东西,按下 'Post' 按钮,标签将显示他们输入的内容。每次用户输入内容时,我希望将其写入磁盘,以便在应用程序退出并再次加载视图时,消息被带回并由标签显示。
目前,标签会显示用户输入的内容,但如果我退出应用程序并重新打开它,标签只会显示 'Your message here',这是我在故事板中分配给它的字符串。
我觉得这可能与 unarchiveObject(withFile:)
调用 and/or 到 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 中.所以这里有一个小的 class 试图做到这一点:
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")
}
}
我可以用这个 class:
写数据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
}
我是不是误解了'Wrap your data in a class'?
更新后的答案:
将 filePath.absoluteString
更改为 filePath.path
。
原回答:
NSCoder
可能无法直接与 Swift 结构一起使用,例如 String
。您需要将数据包装在 class 中并符合 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)
您不需要创建自己的结构,您甚至不需要 Archiver/Unarchiver 一个简单的字符串。可以直接从数据读取和写入字符串。
尝试重写您的代码,使其看起来像这样。
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 = ""
}
}
我对错误的反应有点快,你可以根据你的应用程序来处理它们。