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?
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
}
}
Related
I have the following data architecture, where Orchestra has many Sections, and each Section has many Players :
All 3 classes conform to NSCoding protocol and have the necessary methods implemented. According to this SO question, it should work since NSCoding works recursively.
Inside the Orchestra singleton class, I have the following methods for saving and retrieving Sections:
let sectionArchivalURL: URL = {
let documentDirectories = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
let documentDirectory = documentDirectories.first!
return documentDirectory.appendingPathComponent("sections.archive") //gets archived Player objects
} ()
func saveChanges() -> Bool {
print ("Saving sections to: \(sectionArchivalURL.path)")
return NSKeyedArchiver.archiveRootObject(allSections, toFile: sectionArchivalURL.path)
}
Section also conforms to NSCoding:
//MARK: - NSCoding methods
func encode(with aCoder: NSCoder) {
aCoder.encode(sectionName, forKey: "sectionName")
aCoder.encode(allPlayers, forKey: "allPlayers")
}
required init(coder aDecoder: NSCoder) {
sectionName = aDecoder.decodeObject(forKey: "sectionName") as! String
allPlayers = aDecoder.decodeObject(forKey: "allPlayers") as! [Player]
super.init()
}
Similarly, Player also conforms to NSCoding:
//MARK: - NSCoding methods
func encode(with aCoder: NSCoder) {
aCoder.encode(name, forKey: "playerName")
print ("encoding Player") //does not get called
}
required init(coder aDecoder: NSCoder) {
name = aDecoder.decodeObject(forKey: "playerName") as! String
super.init()
}
Saving is confirmed working. However, when the app relaunches, I am able to view my teams, but the containing Players are empty. I also know that the encoding function in Player did not get called. What am I doing incorrectly?
Leave everything the same except your Player class. Here is the way I got it working:
func encode(with aCoder: NSCoder) {
let encName = NSKeyedArchiver.archivedData(withRootObject: name)
aCoder.encode(encName, forKey: "playerName")
print ("encoding Player")
}
required init(coder aDecoder: NSCoder) {
let tmpName = aDecoder.decodeObject(forKey: "playerName")
name = NSKeyedUnarchiver.unarchiveObject(with: tmpName as! Data) as! String
}
I ran into an issue where I could not use a lazy variable inside init?(coder aDecoder: NSCoder)
My sample code is
class Category: NSObject, NSCoding {
var categoryID: NSInteger!
var categoryName: String!
var categoryLogoURL: String!
lazy var categoryTags = [String]()
private override init() {
}
required init?(coder aDecoder: NSCoder) {
self.categoryID = aDecoder.decodeInteger(forKey: "categoryID")
self.categoryName = aDecoder.decodeObject(forKey: "categoryName") as! String
self.categoryLogoURL = aDecoder.decodeObject(forKey: "categoryLogoURL") as! String
self.categoryTags = aDecoder.decodeObject(forKey: "categoryTags") as! [String]
}
func encode(with aCoder: NSCoder) {
aCoder.encode(self.categoryID, forKey: "categoryID")
aCoder.encode(categoryName, forKey: "categoryName")
aCoder.encode(categoryLogoURL, forKey: "categoryLogoURL")
aCoder.encode(categoryTags, forKey: "categoryTags")
}
}
I am getting an error Use of 'self' in property access 'categoryTags' before super.init initializes self
Once I remove lazy everything works fine. What I am doing wrong?
call Super Init:
required init?(coder aDecoder: NSCoder) {
super.init()
self.categoryID = aDecoder.decodeInteger(forKey: "categoryID")
self.categoryName = aDecoder.decodeObject(forKey: "categoryName") as! String
self.categoryLogoURL = aDecoder.decodeObject(forKey: "categoryLogoURL") as! String
self.categoryTags = aDecoder.decodeObject(forKey: "categoryTags") as! [String]
}
I want to define a c ++ structure as swift code and serialize and deserialize it to write to socket.
class PacketHeader: NSObject, NSCoding {
var ver: UInt8 = 0
var len: UInt32 = 0
required init(coder aDecoder: NSCoder) {
??
}
func encode(with aCoder: NSCoder) {
??
}
}
I can not find a function to decode and encode uint8, uint32.
Xcode 8 (Swift 3) Playground:
class PacketHeader: NSObject, NSCoding {
var ver: UInt8 = 0
var len: UInt32 = 0
override init() {
super.init()
}
required init(coder aDecoder: NSCoder) {
if let ver = aDecoder.decodeObject(forKey: "ver") as? NSNumber {
self.ver = ver.uint8Value
}
if let len = aDecoder.decodeObject(forKey: "len") as? NSNumber {
self.len = len.uint32Value
}
}
func encode(with aCoder: NSCoder) {
aCoder.encode(NSNumber(value: ver), forKey: "ver")
aCoder.encode(NSNumber(value: len), forKey: "len")
}
}
let data = PacketHeader()
data.ver = 10
data.len = 8
let savedData = NSKeyedArchiver.archivedData(withRootObject: data)
let obj = NSKeyedUnarchiver.unarchiveObject(with: savedData) as? PacketHeader
obj?.ver
obj?.len
Here's what you need to do if you don't want to convert to NSNumber, though I suspect that this must be a duplicate question:
class PacketHeader: NSObject, NSCoding {
var ver: UInt8 = 0
var len: UInt32 = 0
init(ver: UInt8, len: UInt32) {
self.ver = ver
self.len = len
super.init()
}
required init(coder aDecoder: NSCoder) {
self.ver = UInt8(aDecoder.decodeCInt(forKey: "ver"))
self.len = UInt32(aDecoder.decodeCInt(forKey: "len"))
}
func encode(with aCoder: NSCoder) {
aCoder.encodeCInt(Int32(self.ver), forKey: "ver")
aCoder.encodeCInt(Int32(self.len), forKey: "len")
}
}
let x = PacketHeader(ver: 75, len: 123)
let e = NSKeyedArchiver.archivedData(withRootObject: x)
if let y = NSKeyedUnarchiver.unarchiveObject(with: e) as? PacketHeader {
print("\(y.ver), \(y.len)") // 75, 123
} else {
print("URK???")
}
I have custom class where I declare
var metros: [[String: AnyObject]] = []
I have to save this into user defaults so I init NSCoding
#objc required init(coder aDecoder: NSCoder) {
metros = (aDecoder.decodeObjectForKey("metros")! as? [[String: AnyObject]])!
}
#objc func encodeWithCoder(aCoder: NSCoder) {
aCoder.encodeObject(metros, forKey: "metros")
}
The problem is while decoding it returns the same dictionary multiple times like [key1: value1, key2: value2] and [key2 : value2, key1 : value1] and so on. How should I avoid this?
To be more exact I have 31 duplicates, 55 duplicates of some of the values
UPDATE:
I solved this issue like
#objc required init(coder aDecoder: NSCoder) {
if let metroObject = (aDecoder.decodeObjectForKey("metros")! as? [[String: AnyObject]]) {
for object in metroObject {
var alreadyExists = false
for objectAppended in metros {
if String(objectAppended["name"]) == String(object["name"]) {
alreadyExists = true
}
}
if alreadyExists == false {
metros.append(object)
print(object["name"])
}
}
}
}
but really curious if there is another way
I'm trying to encode a custom class so I can save it using NSKeyedArchiver.archivedDataWithRootObject
but when I try to conform to the NSCoding protocol, I get this error : 'self' used before self.init. Here is my code:
class MemberRewardsInfo: NSObject, NSCoding {
var id: Int?
required convenience init?(coder aDecoder: NSCoder) {
guard let unarchivedId = aDecoder.decodeObjectForKey("id") as? Int
else {
return nil
}
}
func encodeWithCoder(aCoder: NSCoder) {
aCoder.encodeObject(id, forKey: "id")
}
}
its pretty annoying, not sure why it's not working.
The error message is sort of misleading, but you need to make init(coder:) a designated initializer.
You need to modify your init(code:) to something like this:
required init?(coder aDecoder: NSCoder) {
guard let unarchivedId = aDecoder.decodeObjectForKey("id") as? Int else {
return nil
}
self.id = unarchivedId
super.init()
}
Apparently it's upset about convenience. The assumption is that if you create a convenience initializer, it's going to chain to a "real initializer.
When using a convenience initializer, just call the designated initializer, in your case, self.init(), inside block. See this example in Apple docs for an explanation.
required convenience init?(coder aDecoder: NSCoder) {
guard let unarchivedId = aDecoder.decodeObjectForKey("id") as? Int
else {
return nil
}
self.init()
self.id = unarchivedId
}