3

I have class Foo which conforms to NSObject and NSCoding which I want to be able to persist with NSKeyedArchiver I want to create class Bar, a subclass of Foo that will also conform to NSObject and NSCoding. I am having a problem understanding how to create the required convenience init?(coder aDecoder: NSCoder) in the subclass.

so class Foo...

class Foo: NSObject, NSCoding {
  let identifier:String
  init(identifier:String) {
    self.identifier = identifier
  }

  override var description:String {
    return "Foo: \(identifier)"
  }

  func encodeWithCoder(aCoder: NSCoder) {
    aCoder.encodeObject(identifier, forKey: "identifier")
  }

  required convenience init?(coder aDecoder: NSCoder) {
    guard let identifier = aDecoder.decodeObjectForKey("identifier") as? String
      else {
        return nil
    }
    self.init(identifier:identifier)
  }
}

Then class Bar ...

class Bar:Foo {
  let tag:String

  init(identifier:String, tag:String) {
    self.tag = tag
    super.init(identifier: identifier)
  }

  override var description:String {
    return "Bar: \(identifier) is \(tag)"
  }
}

I can get this to compile by adding the following methods on to make this NSCoding compliant

  override func encodeWithCoder(aCoder: NSCoder) {
    aCoder.encodeObject(tag, forKey: "tag")
    super.encodeWithCoder(aCoder)
  }

this makes sense because I call super.encodeWithCoder(...) reusing the super makes this DRY. The problem I am having is creating the required convenience init?(...) the only way I can seem to get it to compile is by doing this...

  required convenience init?(coder aDecoder:NSCoder) {
    guard let identifier = aDecoder.decodeObjectForKey("identifier") as? String,
          let tag        = aDecoder.decodeObjectForKey("tag") as? String
      else {
        return nil
    }

    self.init(identifier:identifier, tag:tag)
  }

I basically have copied the superclass required initializer and then added the additional decode method for the subclass property. This approach does not seem correct...

Is there a better way to implement this??

0xBitRaptor
  • 169
  • 1
  • 9

3 Answers3

0

Right after you decode and assign all the subclass properties in the required init method, call:

super.init(coder: aDecoder)
Ahmed Onawale
  • 3,992
  • 1
  • 17
  • 21
  • Not exactly sure where you mean to add the call to `super.init(coder:aDecoder)` could you please provide more detail.. thanks – 0xBitRaptor Mar 08 '16 at 13:47
  • [here is a link to an example](http://stackoverflow.com/questions/35865947/nscoding-how-to-create-required-init-in-inherited-classes/35866526#35866526) – Ahmed Onawale Mar 08 '16 at 13:50
0

I have try your code in playground, it just auto to add the code when I tick the red circle of the Error.

The coding is like your function required convenience init.

sample code:

required convenience init?(coder aDecoder: NSCoder)
{
    fatalError("init(coder:) has not been implemented")
}
Zizouz212
  • 4,908
  • 5
  • 42
  • 66
  • This solves the problem of having a required initializer for the purpose of compiling the program. The convenience initializer still needs to be implemented otherwise you will generate the `fatalError` when you try to decode the archived object ie. `NSKeyedUnarchiver.unarchiveObjectWithFile(_:)` – 0xBitRaptor Mar 08 '16 at 13:46
0

Have thought about this for a while and believe that this is the correct way to implement this.

The reason is the way Swift enforces object initialization. Convenience initializers can only call the required initializers on self. Only the required initializer can call the init on super.

Therefore the only way to initialize the subclass object is to decode all of the required initialization parameters before you call the subclass required initializer...which then calls the super class initializer

Here is code you can copy to a playground https://gist.github.com/vorlando/dc29ba98c93eaadbc7b1

0xBitRaptor
  • 169
  • 1
  • 9