Encode/Decoding 在 Swift 3 中与 NSCoder 约会?
Encode/Decoding Date with NSCoder in Swift 3?
我正在编写一个基于 Apple 的 FoodTracker 教程的规划器应用程序,它存储几个字符串、一个日期和每个分配的 UIImage。我在 encoding/decoding 日期遇到了问题。我不确定是哪一个,因为控制台输出会随着每次细微的修改而改变,但据我所知,我的代码将日期保存为 nil,然后当它尝试加载该日期时,它意外地发现 nil 和崩溃。因为我对Swift比较陌生,而且Swift3是个头疼半天的问题,我也不太清楚问题到底出在哪里。这是我 认为 应该工作的代码:
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)
}
如有任何见解,我们将不胜感激。
编辑:
在 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)!
它编译了,但它仍然在 newDueDate = aDecoder.decodeObject(forKey: PropertyKey.dueDateKey) as! Date
行抛出 fatal error: unexpectedly found nil while unwrapping an Optional value
编辑 2:
回到我的原始代码并将 ?
更改为 !
后,这就是我的代码的样子:
//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)
它编译了,但它仍然在 let dueDate = aDecoder.decodeObject(forKey: PropertyKey.dueDateKey) as! Date
行抛出 fatal error: unexpectedly found nil while unwrapping an Optional value
编辑 3(工作):
对于任何感兴趣的人,这里是修改后的原始代码部分:
属性:
//You can't encode optional values, so you don't use `?` here.
var dueDate: Date
var assignmentImage: UIImage
初始化程序:
//Removed the `?` after UIImage because it isn't an Optional.
init?(name: String, className: String, assignmentDescription: String, materials: String, dueDate: Date, assignmentImage: UIImage)
解码器(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)
它并不完美,但它确实有效。
您只能对 Objective-C 对象进行编码,而可选 Date?
不是 Objective-C 对象。试着说:
aCoder.encode(dueDate! as NSDate, forKey: PropertyKey.dueDateKey)
正如 Matt 所说,您不能对可选项进行编码。不过,与其强行解包,我建议添加一个 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)
}
}
然后在你的 init(coder:) 方法中,在解码之前检查密钥是否存在:
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)
}
编辑:
我刚刚在 Github (link) 上创建了一个名为 Swift3PhoneTest 的示例项目,它演示了如何使用 NSSecureCoding
保存自定义数据容器对象。
它同时具有非可选和可选 属性,并在可选 属性 为 nil 时正确管理归档和取消归档。
我正在编写一个基于 Apple 的 FoodTracker 教程的规划器应用程序,它存储几个字符串、一个日期和每个分配的 UIImage。我在 encoding/decoding 日期遇到了问题。我不确定是哪一个,因为控制台输出会随着每次细微的修改而改变,但据我所知,我的代码将日期保存为 nil,然后当它尝试加载该日期时,它意外地发现 nil 和崩溃。因为我对Swift比较陌生,而且Swift3是个头疼半天的问题,我也不太清楚问题到底出在哪里。这是我 认为 应该工作的代码:
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)
}
如有任何见解,我们将不胜感激。
编辑:
在 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)!
它编译了,但它仍然在 newDueDate = aDecoder.decodeObject(forKey: PropertyKey.dueDateKey) as! Date
fatal error: unexpectedly found nil while unwrapping an Optional value
编辑 2:
回到我的原始代码并将 ?
更改为 !
后,这就是我的代码的样子:
//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)
它编译了,但它仍然在 let dueDate = aDecoder.decodeObject(forKey: PropertyKey.dueDateKey) as! Date
fatal error: unexpectedly found nil while unwrapping an Optional value
编辑 3(工作):
对于任何感兴趣的人,这里是修改后的原始代码部分:
属性:
//You can't encode optional values, so you don't use `?` here.
var dueDate: Date
var assignmentImage: UIImage
初始化程序:
//Removed the `?` after UIImage because it isn't an Optional.
init?(name: String, className: String, assignmentDescription: String, materials: String, dueDate: Date, assignmentImage: UIImage)
解码器(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)
它并不完美,但它确实有效。
您只能对 Objective-C 对象进行编码,而可选 Date?
不是 Objective-C 对象。试着说:
aCoder.encode(dueDate! as NSDate, forKey: PropertyKey.dueDateKey)
正如 Matt 所说,您不能对可选项进行编码。不过,与其强行解包,我建议添加一个 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)
}
}
然后在你的 init(coder:) 方法中,在解码之前检查密钥是否存在:
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)
}
编辑:
我刚刚在 Github (link) 上创建了一个名为 Swift3PhoneTest 的示例项目,它演示了如何使用 NSSecureCoding
保存自定义数据容器对象。
它同时具有非可选和可选 属性,并在可选 属性 为 nil 时正确管理归档和取消归档。