Challenge: Persist Page Setup


#1

i get trouble on this topic. can anyone help me. thank you so much! the codes below are anything wrong?

override fun dataOfType(typeName: String, error outError: NSErrorPointer) -> NSData? {
return NSKeyedArchiver.archivedDataWithRootObject([“employees” : employees, “printInfo”: printInfo])
}

override fun readFromData(data: NSData, ofType typeName…) -> Bool {
let dictionary = NSKeyedUnarchiver.unarchiveObjectWithData(data) as! [String : AnyObject]
if let unarchiveEmployees = dictionary[“employees”] as? [Employee] {
employees = unarchiveEmployees
}
if let unarchivedPrintInfo = dictionary[“printInfo”] as? NSPrintInfo {
printInfo = unarchivedPrintInfo
}
return true


#2

What problems do you have? My approach is similar but I set it all a bit more explicitly, i.e. create a [String: AnyObject] and add the key value pairs to it. Then I pass this to the NSKeyedArchiver’s method. As below which I think is equivalent to your code:

[code] override func dataOfType(typeName: String) throws -> NSData {
//End Editing
tableView.window?.endEditingFor(nil)

    //Create an NSData object from the employees array
    
    //Original save system - VER NIL
    /* return NSKeyedArchiver.archivedDataWithRootObject(employees)*/
    
    //Updated save system - VER 1
    var dataDictionary = [String: AnyObject]() //empty dictionary
    
    let versionNumber: Int = 1
    
    dataDictionary[versionKey] = versionNumber
    dataDictionary[employeeKey] = employees
    dataDictionary[printInfoKey] = self.printInfo
    
    return NSKeyedArchiver.archivedDataWithRootObject(dataDictionary)
}[/code]

Note I commented out the original method and I also keep track of the version number.

Then remember that you need to check which version of file you are loading. The old method (in the archiving chapter) is not compatible with your new “dictionary-type” implementation. To get round this I did a simple check.

[code] override func readFromData(data: NSData, ofType typeName: String) throws {
print(“About to read data of type (typeName)”)

    //Is there a dictionary?
    
    if let dictionary = NSKeyedUnarchiver.unarchiveObjectWithData(data) as? [String: AnyObject] {
        //New system
        Swift.print("new save system")
        
        if let version = dictionary[versionKey] as? Int {
            switch version {
            case 1:
                loadData_Ver1(dictionary)
            default:
                self.employees = []
            }
        }
        
    } else {
        //Old system
        Swift.print("old save system")
        employees = NSKeyedUnarchiver.unarchiveObjectWithData(data) as! [Employee]
    }
}[/code]

Hope that helps. load_ver1(:slight_smile: essentially does what you had written.


#3

This challenge is even easier than it looks in terms of backwards compatibility.

The challenge suggests using a dictionary to store both the employees and the print-information. The NSKey(Un)Archiver classes already implement their data storage as a dictionary! When (un)archiving a root-level object, these classes don’t splatter the dictionary, but keep the dictionary and use a special key string for the root object. (Its variable name is “NSKeyedArchiveRootObjectKey”.) So you can define a second key string for the print-information object and go to town and have backwards compatibility.

Maybe an older edition of the book used the obsolete non-keyed (un)archiver classes, which do have the limitations described in the challenge. Maybe the writers didn’t realize (or care about) this.


#4

CTMacUser, would you be willing to elaborate on how exactly do do what you suggest?


#5

Maybe now, is there someone that could elaborate a little on CTMacUser’s “answer”? What would you use as the root object if you don’t create your own dictionary and have the version number, employees, and printInfo in it? I may be thinking of this incorrectly, but if you call the archive with the employees array as the root object it won’t include the printInfo in the archive will it? Do you call the archive on NSKeyedArchiveRootObjectKey? I tried that and I get errors when tying to read a file saved that way.

I got it to work using the book’s recommendation of creating a dictionary to use as the root object, but if there is a more eloquent way to do this, I’d like to learn how.

override func data(ofType typeName: String) throws -> Data {
	// End editing
	tableView.window?.endEditing(for: nil)

	// Create an NSData object from the employees array
	// old save
//		return NSKeyedArchiver.archivedData(withRootObject: employees)
	
	// updated for Chapter 27 challenge
	//Updated save sytem - Version 1
	
	let versionNumber: Int = 1
	
	let dataDictionary: [String: AnyObject] = [
		"versionKey": versionNumber as AnyObject,
		"employeeKey": employees as AnyObject,
		"printInfoKey": self.printInfo]

	return NSKeyedArchiver.archivedData(withRootObject: dataDictionary)  // dataDictionary
}

override func read(from data: Data, ofType typeName: String) throws {
	Swift.print("About to read data of type \(typeName).")
//		employees = NSKeyedUnarchiver.unarchiveObject(with: data) as! [Employee]
	
	// This is the new Save section
	
	// Is there a Dictionary?
	if let dictionary = NSKeyedUnarchiver.unarchiveObject(with: data) as? [String: AnyObject] {
		// New System
		Swift.print("New Save System")
		if let version = dictionary["versionKey"] as? Int {
			switch version {
			case 1:
				employees = (dictionary["employeeKey"] as? [Employee])!
				self.printInfo = (dictionary["printInfoKey"] as? NSPrintInfo)!
			default:
				self.employees = []
			}
		}
	} else {
		// Old System
		Swift.print("Old Save System")
		employees = NSKeyedUnarchiver.unarchiveObject(with: data) as! [Employee]
	}
	Swift.print("Done reading data")
}

#6

Using a dictionary for the root object is good; you don’t need to create a new class for the root object.

However, you can make use of some good coding practices.

  1. Define type names and use them for the types you repeat.
typealias VersionNumber = Int
typealias EmployeeStore = [Employee]
typealias RootObjectDictionary = [String: AnyObject]
  1. Define names and use them for the keys instead of repeating string literals.
struct ArchiveKey {
   static let version    = "versionKey"
   static let employees  = "employeesKey"
   static let printInfo  = "printInfoKey"
}
let versionNumber: VersionNumber = 1

let dataDictionary: RootObjectDictionary = [
     ArchiveKey.version    : versionNumber as AnyObject,
     ArchiveKey.employees  : employees as AnyObject,
     ArchiveKey.printInfo  : self.printInfo
]
if let dictionary = NSKeyedUnarchiver.unarchiveObject (with: data) as? RootObjectDictionary {
...
   if let version = dictionary [ArchiveKey.version] as? VersionNumber {
...
}

Also check out the new Swift way of archiving: Encoding and Decoding Custom Types.


#7

Thank you so much for pointing out good coding practices. That is exactly what I’m trying to get engrained into my coding. I had seen examples similar to what you suggested in the book, but it hasn’t stuck yet…apparently! I have added your suggestions to my code.

Thank you for your help ibex10…again

Also, thanks for the link to more studying.


#8

As I read the Encoding and Decoding Custom Types, I realized I had come across that before. I have tried a few things, but have been unsuccessful at converting the RaiseMan App to the Codable protocol. It seems like it would be much easier to use.

But when I tried to implement it by changing NSCoding protocol to the Codable protocol in the Employee class and commented out the NSCoding required encode and required init functions then made this change to the following in Document.Swift

	override func data(ofType typeName: String) throws -> Data {
	// End editing
	tableView.window?.endEditing(for: nil)

	// Create an NSData object from the employees array

	// updated for Chapter 27 challenge
	//Updated save sytem - Version 1

	let versionNumber: VersionNumber = 1

	let dataDictionary: RootObjectDictionary = [
		ArchiveKey.version 	: versionNumber as AnyObject,
		ArchiveKey.employees 	: employees as AnyObject,
		ArchiveKey.printInfo 	: self.printInfo
	]
	return JSONEncoder().encode(dataDictionary)

//       instead of: return NSKeyedArchiver.archivedData(withRootObject: dataDictionary)
}

it wouldn’t compile showing a “Generic parameter ‘T’ could not be inferred” error. If I don’t try to encode the dataDictionary, the encoding will be back where I was before, just encoding employees and not including the printInfo. I haven’t found yet if printInfo (an instance of NSPrintInfo) would be Codable. It doesn’t seem like it is.

At least I know it works with NSCoding until Apple depreciates it.