Saturday, June 7, 2014

NSCoding Protocol - Swift Version

So, Swift is here and I'm working out examples of using Swift with the original frameworks. Here is a version of my NSCoding Protocol post using Swift. I split the Car class and main into two files: Car.swift and main.swift.

Car.swift:

//
//  Car.swift
//  SwiftArchiverDemo
//
//  Created by Tom Limbaugh on 6/7/14.
//

import Foundation

protocol NSCoding {
    
}

class Car:NSObject {
    
    var year: Int = 0
    var make: String = ""
    var model: String = ""
    
    init() {
        super.init()
    }
    
    func encodeWithCoder(aCoder: NSCoder!) {
        aCoder.encodeInteger(year, forKey:"year")
        aCoder.encodeObject(model, forKey:"make")
        aCoder.encodeObject(make, forKey:"model")
    }
    
    init(coder aDecoder: NSCoder!) {
        
        super.init()
        
        year = aDecoder.decodeIntegerForKey("year")
        make = aDecoder.decodeObjectForKey("make") as String
        model = aDecoder.decodeObjectForKey("model") as String
        
    }
}


main.swift creates three instances of the Car class for demonstrating the archiving and unarchiving of the objects.

//
//  main.swift
//  SwiftArchiverDemo
//
//  Created by Tom Limbaugh on 6/7/14.
//

import Foundation

var documentDirectories:NSArray
var documentDirectory:String
var path:String
var unarchivedCars:NSArray
var allCars:NSArray

// Create a filepath for archiving.
documentDirectories = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)

// Get document directory from that list
documentDirectory = documentDirectories.objectAtIndex(0) as String

// append with the .archive file name
path = documentDirectory.stringByAppendingPathComponent("swift_archiver_demo.archive")

var car1:Car! = Car()
var car2:Car! = Car()
var car3:Car! = Car()

car1.year = 1957
car1.make = "Chevrolet"
car1.model = "Bel Air"

car2.year = 1964
car2.make = "Dodge"
car2.model = "Polara"

car3.year = 1972
car3.make = "Plymouth"
car3.model = "Fury"

allCars = [car1, car2, car3]

// The 'archiveRootObject:toFile' returns a bool indicating
    // whether or not the operation was successful. We can use that to log a message.
if NSKeyedArchiver.archiveRootObject(allCars, toFile: path) {
    println("Success writing to file!")
} else {
    println("Unable to write to file!")
}
    
// Now lets unarchive the data and put it into a different array to verify
// that this all works. Unarchive the objects and put them in a new array
unarchivedCars = NSKeyedUnarchiver.unarchiveObjectWithFile(path) as NSArray
    
// Output the new array
for car : AnyObject in unarchivedCars {
    println("Here's a \(car.year) \(car.make) \(car.model)")

}

As with the original Objective-C post, if you’re running this as a command line tool, your *.archive file should be found in your '/Users/username/Documents directory'. If you open it with TextEdit.app, you should see a cryptic representation of the string data from the original objects.

7 comments:

  1. Do these functions get called automatically or do we need to make an explicit call ?
    func encodeWithCoder(aCoder: NSCoder!) {...}
    init(coder aDecoder: NSCoder!) {...}

    ReplyDelete
    Replies
    1. Some protocols have required methods or functions. These are both required methods/functions in the NSCoding Protocol. So, you must call/implement these methods/functions.

      From the Xcode documentation -
      "The NSCoding protocol declares the two methods that a class must implement so that instances of that class can be encoded and decoded."

      If you are retrieving data from a file, you must init(coder decoder: NSCoder!) to instantiate the objects that you are decoding. The Swift call omits the word 'With' in init methods/functions. In Objective-C the call looks like this: - (id)initWithCoder:(NSCoder *)decoder

      Delete
  2. Hi, I get a few errors in the main.swift file. Any thoughts?

    ReplyDelete
  3. Insert the keyword 'override', before calling 'init'. Apparently, this was not necessary in an earlier beta version of Xcode. The compiler should have suggested that. It did on my end and works fine now. The init method should now look like this:

    override init() {
    super.init()
    }

    ReplyDelete
  4. Thanks. I figured out that I also had to make to downcast as follows:

    for car in unarchivedCars as [Car!] {
    println("Here's a \(car.year) \(car.make) \(car.model)")
    }

    ReplyDelete
  5. Hi, I think there was a change made to the NSCoding protocol. See here:
    http://stackoverflow.com/questions/25750088/swift-does-not-conform-to-protocol-nscoding

    Basically, take out the ! in both methods and add 'required' to init(coder aDecoder: NSCoder)

    ReplyDelete
  6. Thanks for posting this. I have OSX 10.9 installed so I had to copy the 10.10 SDK from the Xcode 6 Beta for my current Xcode 6 GM installation. This is necessary for any Swift OSX command line tools that I build on my machine. That being said, The code above still compiles and runs on my machine as written, with the August 9, 2014 edit from above. So, If I understand correctly, the SDK that ships with OSX 10.10 would have made your changes necessary.

    ReplyDelete