使用Swift 3中的NSCoder编码/解码日期?

H. *_*tin 2 nscoder ios swift

我正在编写一个基于Apple的FoodTracker教程的计划器应用程序,它可以存储多个字符串,一个日期和每个分配的UIImage.我遇到编码/解码日期的问题.我不确定它是什么因为控制台输出随着每次轻微的修改而改变,但从我所知道的,我的代码将Date保存为nil,然后当它试图加载该Date时,它意外地发现nil和崩溃.因为我对Swift相对较新,而Swift 3是一个令人头疼的问题,我几乎不知道问题究竟在哪里.这是我认为应该工作的代码:

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)
}
Run Code Online (Sandbox Code Playgroud)

任何见解都将不胜感激.

编辑:

有了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)!
Run Code Online (Sandbox Code Playgroud)

它编译,但它仍然fatal error: unexpectedly found nil while unwrapping an Optional value在线上newDueDate = aDecoder.decodeObject(forKey: PropertyKey.dueDateKey) as! Date

编辑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)
Run Code Online (Sandbox Code Playgroud)

它编译,但它仍然fatal error: unexpectedly found nil while unwrapping an Optional value在线上let dueDate = aDecoder.decodeObject(forKey: PropertyKey.dueDateKey) as! Date

编辑3(工作):

对于任何感兴趣的人,以下是原始代码的部分,这些部分经过修改以使其工作:

属性:

//You can't encode optional values, so you don't use `?` here.
var dueDate: Date
var assignmentImage: UIImage
Run Code Online (Sandbox Code Playgroud)

初始化:

//Removed the `?` after UIImage because it isn't an Optional.
init?(name: String, className: String, assignmentDescription: String, materials: String, dueDate: Date, assignmentImage: UIImage)
Run Code Online (Sandbox Code Playgroud)

解码器(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) 
Run Code Online (Sandbox Code Playgroud)

它并不完美,但它确实有效.

Dun*_*n C 8

正如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)
        }
    }
Run Code Online (Sandbox Code Playgroud)

然后在你的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)
}
Run Code Online (Sandbox Code Playgroud)

编辑:

我刚刚在Github(链接)上创建了一个名为Swift3PhoneTest的示例项目,该项目演示了如何使用NSSecureCoding保存自定义数据容器对象.

它具有非可选属性和可选属性,并在可选属性为nil时正确管理归档和取消归档.