[英]Encode/Decoding Date with NSCoder in Swift 3?
I'm writing a planner app, based on Apple's FoodTracker tutorial, that stores several strings, a Date, and a UIImage per Assignment. 我正在编写一个基于Apple的FoodTracker教程的计划器应用程序,它可以存储多个字符串,一个日期和每个分配的UIImage。 I'm encountering problems encoding/decoding the Date.
我遇到编码/解码日期的问题。 I don't know for sure which it is because the console outputs change with every slight modification, but from what I can tell, my code saves the Date as nil, and then when it tries to load that Date, it unexpectedly finds nil and crashes.
我不确定它是什么因为控制台输出随着每次轻微的修改而改变,但从我所知道的,我的代码将Date保存为nil,然后当它试图加载该Date时,它意外地发现nil和崩溃。 Because I'm relatively new to Swift and Swift 3 is a headache and a half, I have very little idea where the problem really is.
因为我对Swift相对较新,而Swift 3是一个令人头疼的问题,我几乎不知道问题究竟在哪里。 Here's the code that I think should work:
这是我认为应该工作的代码:
class Assignment: NSObject, NSCoding {
//MARK: Properties
var name: String
var className: String
var assignmentDescription: String
var materials: String
var dueDate: Date?
var assignmentImage: UIImage?
//MARK: Archiving Paths
static let DocumentsDirectory = FileManager().urls(for: .documentDirectory, in: .userDomainMask).first!
static let ArchiveURL = DocumentsDirectory.appendingPathComponent("assignments")
//MARK: Types
struct PropertyKey {
static let nameKey = "name"
static let classNameKey = "className"
static let assignmentDescriptionKey = "assignmentDescription"
static let materialsKey = "materials"
static let dueDateKey = "dueDate"
static let assignmentImageKey = "assignmentImage"
}
//MARK: Initialization
init?(name: String, className: String, assignmentDescription: String, materials: String, dueDate: Date, assignmentImage: UIImage?) {
//Initialize stored properties.
self.name = name
self.className = className
self.assignmentDescription = assignmentDescription
self.materials = materials
self.dueDate = dueDate
self.assignmentImage = assignmentImage
super.init()
//Initialization should fail if there is no name and no class.
if name.isEmpty && className.isEmpty {
print("Failed to initialize an assignment.")
return nil
}
}
//MARK: NSCoding
func encode(with aCoder: NSCoder) {
aCoder.encode(name, forKey: PropertyKey.nameKey)
aCoder.encode(className, forKey: PropertyKey.classNameKey)
aCoder.encode(assignmentDescription, forKey: PropertyKey.assignmentDescriptionKey)
aCoder.encode(materials, forKey: PropertyKey.materialsKey)
aCoder.encode(dueDate, forKey: PropertyKey.dueDateKey)
aCoder.encode(assignmentImage, forKey: PropertyKey.dueDateKey)
}
required convenience init?(coder aDecoder: NSCoder) {
//Required fields.
let name = aDecoder.decodeObject(forKey: PropertyKey.nameKey) as! String
let className = aDecoder.decodeObject(forKey: PropertyKey.classNameKey) as! String
//Optional fields.
let assignmentDescription = aDecoder.decodeObject(forKey: PropertyKey.assignmentDescriptionKey) as? String
let materials = aDecoder.decodeObject(forKey: PropertyKey.materialsKey) as? String
let dueDate = aDecoder.decodeObject(forKey: PropertyKey.dueDateKey) as! Date
let assignmentImage = aDecoder.decodeObject(forKey: PropertyKey.assignmentImageKey) as? UIImage
//Must call designated initializer.
self.init(name: name, className: className, assignmentDescription: assignmentDescription!, materials: materials!, dueDate: dueDate, assignmentImage: assignmentImage)
}
Any insight at all would be appreciated. 任何见解都将不胜感激。
Edit: 编辑:
With Duncan C's help and Xcode's fix-its, this is what required convenience init?(coder aDecoder: NSCoder)
looks like now: 有了Duncan C的帮助和Xcode的修复 - 它,这是什么
required convenience init?(coder aDecoder: NSCoder)
现在看起来像:
//Required fields.
let newName = aDecoder.decodeObject(forKey: PropertyKey.nameKey) as! String
let newClassName = aDecoder.decodeObject(forKey: PropertyKey.classNameKey) as! String
//Optional fields.
var newAssignmentImage: UIImage?
var newDueDate: Date
let newAssignmentDescription = aDecoder.decodeObject(forKey: PropertyKey.assignmentDescriptionKey) as! String
let newMaterials = aDecoder.decodeObject(forKey: PropertyKey.materialsKey) as! String
if aDecoder.containsValue(forKey: PropertyKey.dueDateKey) {
newDueDate = aDecoder.decodeObject(forKey: PropertyKey.dueDateKey) as! Date
} else {
newDueDate = Date()
if aDecoder.containsValue(forKey: PropertyKey.assignmentImageKey) {
newAssignmentImage = aDecoder.decodeObject(forKey: PropertyKey.assignmentImageKey) as? UIImage
} else {
newAssignmentImage = UIImage(named: "sampleAssignmentImage")
}
}
//Must call designated initializer.
self.init(name: newName, className: newClassName, assignmentDescription: newAssignmentDescription, materials: newMaterials, dueDate: newDueDate, assignmentImage: newAssignmentImage)!
It compiles, but it still throws fatal error: unexpectedly found nil while unwrapping an Optional value
on the line newDueDate = aDecoder.decodeObject(forKey: PropertyKey.dueDateKey) as! Date
它编译,但它仍然会引发
fatal error: unexpectedly found nil while unwrapping an Optional value
在newDueDate = aDecoder.decodeObject(forKey: PropertyKey.dueDateKey) as! Date
行中fatal error: unexpectedly found nil while unwrapping an Optional value
newDueDate = aDecoder.decodeObject(forKey: PropertyKey.dueDateKey) as! Date
newDueDate = aDecoder.decodeObject(forKey: PropertyKey.dueDateKey) as! Date
Edit 2: 编辑2:
After going back to my original code and changing ?
回到我的原始代码并改变
?
to !
来
!
, this is what my code looks like: ,这就是我的代码:
//Required fields.
let name = aDecoder.decodeObject(forKey: PropertyKey.nameKey) as! String
let className = aDecoder.decodeObject(forKey: PropertyKey.classNameKey) as! String
//Optional fields.
let assignmentDescription = aDecoder.decodeObject(forKey: PropertyKey.assignmentDescriptionKey) as! String
let materials = aDecoder.decodeObject(forKey: PropertyKey.materialsKey) as! String
let dueDate = aDecoder.decodeObject(forKey: PropertyKey.dueDateKey) as! Date
let assignmentImage = aDecoder.decodeObject(forKey: PropertyKey.assignmentImageKey) as! UIImage
//Must call designated initializer.
self.init(name: name, className: className, assignmentDescription: assignmentDescription, materials: materials, dueDate: dueDate, assignmentImage: assignmentImage)
It compiles, but it still throws fatal error: unexpectedly found nil while unwrapping an Optional value
on the line let dueDate = aDecoder.decodeObject(forKey: PropertyKey.dueDateKey) as! Date
它编译,但它仍然会抛出
fatal error: unexpectedly found nil while unwrapping an Optional value
let dueDate = aDecoder.decodeObject(forKey: PropertyKey.dueDateKey) as! Date
行上的fatal error: unexpectedly found nil while unwrapping an Optional value
let dueDate = aDecoder.decodeObject(forKey: PropertyKey.dueDateKey) as! Date
let dueDate = aDecoder.decodeObject(forKey: PropertyKey.dueDateKey) as! Date
Edit 3 (Working): 编辑3(工作):
For anyone interested, here are the sections of the original code that were modified to make it work: 对于任何感兴趣的人,以下是原始代码的部分,这些部分经过修改以使其工作:
Properties: 属性:
//You can't encode optional values, so you don't use `?` here.
var dueDate: Date
var assignmentImage: UIImage
Initializer: 初始化:
//Removed the `?` after UIImage because it isn't an Optional.
init?(name: String, className: String, assignmentDescription: String, materials: String, dueDate: Date, assignmentImage: UIImage)
Decoder ( required convenience init?(coder aDecoder: NSCoder)
): 解码器(
required convenience init?(coder aDecoder: NSCoder)
):
//Required fields.
let name = aDecoder.decodeObject(forKey: PropertyKey.nameKey) as! String
let className = aDecoder.decodeObject(forKey: PropertyKey.classNameKey) as! String
//Optional fields.
let assignmentDescription = aDecoder.decodeObject(forKey: PropertyKey.assignmentDescriptionKey) as! String //This String should use `!` instead of `?`.
let materials = aDecoder.decodeObject(forKey: PropertyKey.materialsKey) as! String //This String should use `!` instead of `?`.
let dueDate = aDecoder.decodeObject(forKey: PropertyKey.dueDateKey) as! Date
//Check for the UIImage being nil. If it is, assign some default image to it so that it doesn't unwrap nil and crash.
var assignmentImage: UIImage!
if aDecoder.decodeObject(forKey: PropertyKey.assignmentImageKey) == nil {
assignmentImage = UIImage(named: "SampleAssignmentImage")
}
else {
assignmentImage = aDecoder.decodeObject(forKey: PropertyKey.assignmentImageKey) as! UIImage
}
//Must call designated initializer.
//`materials` and `assignmentDescription` don't need `!` now because they're already unwrapped.
self.init(name: name, className: className, assignmentDescription: assignmentDescription, materials: materials, dueDate: dueDate, assignmentImage: assignmentImage)
It isn't perfect, but it works. 它并不完美,但它确实有效。
As Matt, says, you can't encode an optional. 正如Matt所说,你无法对可选项进行编码。 Rather than force-unwrapping it, though, I would suggest adding an
if let
and only adding the optionals to the archive if they contain a value: 但是,我建议添加一个
if let
并且仅在归档中添加一个选项,如果它们包含一个值,而不是强制解包它:
func encode(with aCoder: NSCoder) {
aCoder.encode(name, forKey: PropertyKey.nameKey)
aCoder.encode(className, forKey: PropertyKey.classNameKey)
aCoder.encode(assignmentDescription, forKey: PropertyKey.assignmentDescriptionKey)
aCoder.encode(materials, forKey: PropertyKey.materialsKey)
if let date = dueDate {
aCoder.encode(date, forKey: PropertyKey.dueDateKey)
}
if let image = assignmentImage {
aCoder.encode(image, forKey: PropertyKey.dueDateKey)
}
}
And then in your init(coder:) method, check to see if the keys exist before decoding: 然后在你的init(编码器:)方法中,在解码之前检查密钥是否存在:
required convenience init?(coder aDecoder: NSCoder) {
//Required fields.
name = aDecoder.decodeObject(forKey: PropertyKey.nameKey) as! String
className = aDecoder.decodeObject(forKey: PropertyKey.classNameKey) as! String
//Optional fields.
assignmentDescription = aDecoder.containsValue(forKey: PropertyKey.assignmentDescriptionKey) as? String
materials = aDecoder.decodeObject(forKey: PropertyKey.materialsKey) as? String
if aDecoder.containsValue(forKey: PropertyKey.dueDateKey) {
dueDate = aDecoder.decodeObject(forKey: PropertyKey.dueDateKey) as! Date
} else {
dueDate = nil
if aDecoder.containsValue(forKey: PropertyKey.assignmentImageKey) {
assignmentImage = aDecoder.decodeObject(forKey: PropertyKey.assignmentImageKey) as? UIImage
} else {
assignmentImage = nil
}
//Must call designated initializer.
self.init(name: name, className: className, assignmentDescription: assignmentDescription!, materials: materials!, dueDate: dueDate, assignmentImage: assignmentImage)
}
I just created a sample project called Swift3PhoneTest on Github (link) that demonstrates using NSSecureCoding
to save a custom data container object. 我刚刚在Github(链接)上创建了一个名为Swift3PhoneTest的示例项目,它演示了使用
NSSecureCoding
来保存自定义数据容器对象。
It has both a non-optional and an optional property, and properly manages archiving and unarchiving when the optional property is nil. 它具有非可选属性和可选属性,并在可选属性为nil时正确管理归档和取消归档。
You can only encode an Objective-C object, and an Optional Date?
您只能编码Objective-C对象和可选
Date?
is not an Objective-C object. 不是Objective-C对象。 Try saying:
试着说:
aCoder.encode(dueDate! as NSDate, forKey: PropertyKey.dueDateKey)
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.