3

The decodeDouble on NSCoder returns a non-optional value, but I would like to identify whether a value was nil before it was encoded.

This is my scenario:

var optionalDouble: Double? = nil

func encode(with aCoder: NSCoder) {
    if let optionalDouble {
        aCoder.encode(optionalDouble, forKey: "myOptionalDouble")
    }
}

convenience required init?(coder aDecoder: NSCoder) {
    optionalDouble = aDecoder.decodeDouble(forKey: "myOptionalDouble") 
    // here optionalDouble is never nil anymore
}

So decoding double returns 0 in case the value was never set, so it seems like I can't identify whether a value was actually 0 or nil before encoding

Is there a way for me to check if a double was nil before it was encoded?

MarkHim
  • 5,686
  • 5
  • 32
  • 64
  • you can convert to `NSNumber?` to save with encodeObject and use `decodeObject` instead of `decodeDouble` – Duyen-Hoa Nov 24 '16 at 11:21
  • Do you need to be able to distinguish between the value being encoded with a value of `nil` and the value not existing for the key? – Hamish Nov 24 '16 at 12:53
  • Does [this answer](http://stackoverflow.com/questions/39891190/how-do-i-decode-a-double-using-nscoder-nsobject-and-swift-3-for-ios/39895068#39895068) useful to your case? – Ahmad F Nov 24 '16 at 13:02
  • Hoa yes, that's what I'm doing right now. But I thought there might be a prettier solution out there. Hamish no, nil or not set could be the same. Ahmad no, because that answer is not really correct, since a post-decoding cast to Swift? doesn't change the optionality - (casting 0 to Double? will still be 0) – MarkHim Nov 24 '16 at 15:40

1 Answers1

6

The solution is to use NSNumber instead of Double when you encode, then use decodeObject to get back (if it exists) the double value. For example

class A: NSCoding {
    var optionalDouble: Double? = nil

    @objc func encodeWithCoder(aCoder: NSCoder) {
        if let optionalDouble = optionalDouble {
            aCoder.encodeObject(NSNumber(double: optionalDouble), forKey: "myOptionalDouble")
        }
    }

    @objc required init?(coder aDecoder: NSCoder) {
        if let decodedDoubleNumber = aDecoder.decodeObjectForKey("myOptionalDouble") as? NSNumber {
            self.optionalDouble = decodedDoubleNumber.doubleValue
        } else {
            self.optionalDouble = nil
        }
    }
}

With suggestion from @Hamish, here is the version for Swift 3. Be aware that we need to inherit the class to NSObject in order to make NSEncoding work (Got Unrecognized selector -replacementObjectForKeyedArchiver: crash when implementing NSCoding in Swift)

class A: NSObject, NSCoding {
    var optionalDouble: Double? = nil

    func encode(with aCoder: NSCoder) {
        if let optionalDouble = optionalDouble {
            aCoder.encode(optionalDouble, forKey: "myOptionalDouble")
        }
    }

    override init() {
        super.init()
    }

    required init?(coder aDecoder: NSCoder) {
        optionalDouble = aDecoder.decodeObject(forKey: "myOptionalDouble") as? Double
    }
}
Community
  • 1
  • 1
Duyen-Hoa
  • 15,384
  • 5
  • 35
  • 44
  • 5
    Note that there's no need to go via `NSNumber` explicitly – in Swift 3, just saying `aCoder.encode(optionalDouble, forKey: "myOptionalDouble")` will bridge the `Double` to `NSNumber`, if `optionalDouble` is non-nil. You can also just say `as? Double` in the decode. – Hamish Nov 24 '16 at 13:18