2

I want to subclass CAShapeLayer so it will hold a reference to the corresponding object in the data model:

class CPCoursePointLayer : CAShapeLayer
{
    let itsDataRef :CPCoursePoint
    let itsEventData :CPEventData

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
    }

    init( coursePoint: CPCoursePoint, in eventData :CPEventData)
    {
        itsDataRef = coursePoint
        itsEventData = eventData
    }
}

Where itsDataRefand itsEventData are references (pointers if you like) to the underlying data. This way I can update the model if I have moved the an instance of may layer class, or draw it using graphic styles specified in the eventData. My CPCoursePointLayerdoes not own these variables, to use a Rust term.

Now that I'm required to implement a init(coder:) initializer, how do I initialize my instance variables from nothing? Can I decode a 'pointer' from the NSCoder? That doesn't make sense to me...

Although I'm never going to call init(coder:), myself, I guess it's there because the system might call it. Then I will have an instance that is unusable...

I want to avoid the extra burden of doing the extra if let in every method if I declare my instance variables as optional (?).

jscs
  • 63,694
  • 13
  • 151
  • 195
user29809
  • 85
  • 7
  • 1
    You can implement the method and just have it throw a fatal error since you won't be using it. – dan Jan 14 '19 at 21:43

2 Answers2

2

You essentially have three options:

  1. Declare the variables as implicitly unwrapped optionals
  2. Have init?(coder:) fail, either fatally or not
  3. Have CPCoursePoint and CPEventData implement unarchiving via init?(coder:)

Option 1:

class CPCoursePointLayer : CAShapeLayer
{
    let itsDataRef :CPCoursePoint!
    let itsEventData :CPEventData!

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
    }

    init( coursePoint: CPCoursePoint, in eventData :CPEventData)
    {
        itsDataRef = coursePoint
        itsEventData = eventData
    }
}

This will result in a crash if you reference itsDataRef or itsEventData and your object wasn't initialised by your new initialiser.

Option 2(a)

class CPCoursePointLayer : CAShapeLayer
{
    let itsDataRef :CPCoursePoint
    let itsEventData :CPEventData

    required init?(coder aDecoder: NSCoder) {
        return nil
    }

    init( coursePoint: CPCoursePoint, in eventData :CPEventData)
    {
        itsDataRef = coursePoint
        itsEventData = eventData
    }
}

This will simply fail to initialise the object if init(coder:) is used.

Option 2(b)

class CPCoursePointLayer : CAShapeLayer
{
    let itsDataRef :CPCoursePoint
    let itsEventData :CPEventData

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) is not supported for this class"
    }

    init( coursePoint: CPCoursePoint, in eventData :CPEventData)
    {
        itsDataRef = coursePoint
        itsEventData = eventData
    }
}

This will cause your program to crash if init(coder:) is called.

Option 3

class CPCoursePointLayer : CAShapeLayer
{
    let itsDataRef :CPCoursePoint
    let itsEventData :CPEventData

    required init?(coder aDecoder: NSCoder) {
        guard let itsDataRef = aDecoder.decodeObject(forKey:"itsDataRef"),
              let itsEventData = aDecoder.decodeObject(forKey:"itsEventData") else {
            return nil
        }
        self.itsDataRef = itsDataRef
        self.itsEventData = itsEventData
        super.init(coder: aDecoder)
    }

    init( coursePoint: CPCoursePoint, in eventData :CPEventData)
    {
        itsDataRef = coursePoint
        itsEventData = eventData
    }
}

Option 2(b) is probably the most common as it is a "fail fast" - You should catch this during testing pretty quickly if that initialiser is called.

Paulw11
  • 108,386
  • 14
  • 159
  • 186
1

The typical solution, if you don't want to support NSCoding in your subclass, is to fatalError in init(coder:). In fact, the compiler will even offer that as a fix-it if you omit the initializer entirely:

fix-it

So just do that:

class CPCoursePointLayer: CAShapeLayer {
    let itsDataRef: CPCoursePoint
    let itsEventData: CPEventData

    init(coursePoint: CPCoursePoint, in eventData: CPEventData) {
        itsDataRef = coursePoint
        itsEventData = eventData
        super.init()
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

}

The system does not normally try to decode an instance of your custom layer.

rob mayoff
  • 375,296
  • 67
  • 796
  • 848