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???")
}
Related
I'm relatively new to iOS development in general, but I'm coding this app.
I have custom objects "Semester", "Course", and "Assignments". Semesters contain arrays of courses and courses contain arrays of assignments. I'm able to properly archive the semester and course information, but for some reason the assignment information can't be read.
Semester:
required init?(coder aDecoder: NSCoder) {
self.name = ""
super.init()
if let archivedName = aDecoder.decodeObject(forKey: "name") as? String {
name = archivedName
}
if let archivedCourses = aDecoder.decodeObject(forKey: "courses") as? [Course] {
courses = archivedCourses
}
}
func encode(with aCoder: NSCoder) {
aCoder.encode(name, forKey: "name")
aCoder.encode(courses, forKey: "courses")
}
Course:
required init?(coder aDecoder: NSCoder) {
self.name = ""
super.init()
if let archivedName = aDecoder.decodeObject(forKey: "courseName") as? String {
name = archivedName
}
if let archivedAssignments = aDecoder.decodeObject(forKey: "assignments") as? [Assignment] {
assignments = archivedAssignments
}
}
func encode(with aCoder: NSCoder) {
aCoder.encode(name, forKey: "courseName")
aCoder.encode(assignments, forKey: "assignments")
Assignment:
required init?(coder aDecoder: NSCoder) {
self.name = ""
self.grade = 0
self.weight = 0
super.init()
if let archivedName = aDecoder.decodeObject(forKey: "assignmentName") as? String, let archivedGrade = aDecoder.decodeObject(forKey: "assignmentGrade") as? Int, let archivedWeight = aDecoder.decodeObject(forKey: "assignmentWeight") as? Int {
name = archivedName
grade = archivedGrade
weight = archivedWeight
}
}
func encode(with aCoder: NSCoder) {
aCoder.encode(name, forKey: "assignmentName")
aCoder.encode(grade, forKey: "assignmentGrade")
aCoder.encode(weight, forKey: "assignmentWeight")
}
I'm not sure why but the assignment details can't be read when I try to decode the object.
I Usually decode Int like this
if decoder.containsValue(forKey: "teamNumber"){
self.teamNumber = decoder.decodeInteger(forKey: "teamNumber")
}
translated to your issue will be like this, change the init?(coder aDecoder: NSCoder)method of your Assignment class
required init?(coder aDecoder: NSCoder) {
self.name = ""
self.grade = 0
self.weight = 0
super.init()
if let archivedName = aDecoder.decodeObject(forKey: "assignmentName") as? String{
self.name = archivedName
}
if decoder.containsValue(forKey: "assignmentGrade"){
self.grade = decoder.decodeInteger(forKey: "assignmentGrade")
}
if decoder.containsValue(forKey: "assignmentWeight"){
self.weight = decoder.decodeInteger(forKey: "assignmentWeight")
}
}
Hope this helps
When try to encode my custom object in iOS swift get this error from Xcode 8.3
unrecognized selector sent to instance 0x60800166fe80
*** -[NSKeyedArchiver dealloc]: warning: NSKeyedArchiver deallocated without having had -finishEncoding called on it.
And my code like this:
import UIKit
import Foundation
class Place: NSObject {
func setCustomObject(CustomObject obj:Any,Key key:String) {
let encodedObject : Data = NSKeyedArchiver.archivedData(withRootObject: obj)
UserDefaults.standard.set(encodedObject, forKey: key)
}
}
Here's an example how to make an object to conform to NSCoding. Basically you need to provide implementation of two methods - required convenience init?(coder decoder: NSCoder) and encode(with aCoder: NSCoder)
class Book: NSObject, NSCoding {
var title: String?
var pageCount: Int?
// Memberwise initializer
init(title: String,pageCount: Int) {
self.title = title
self.pageCount = pageCount
}
// MARK: NSCoding
// Here you will try to initialize an object from archve using keys you did set in `encode` method.
required convenience init?(coder decoder: NSCoder) {
guard let title = decoder.decodeObject(forKey: "title") as? String else { return nil }
self.init(title: title, pageCount: decoder.decodeInteger(forKey: "pageCount"))
}
// Here you need to set properties to specific keys in archive
func encode(with aCoder: NSCoder) {
aCoder.encode(self.title, forKey: "title")
aCoder.encodeCInt(Int32(self.pageCount), forKey: "pageCount")
}
}
Also I would recommend changing your setCustomObject method to this:
func setCustomObject(obj:NSCoding, key:String) {
let encodedObject : Data = NSKeyedArchiver.archivedData(withRootObject: obj)
UserDefaults.standard.set(encodedObject, forKey: key)
}
This way compiler prevent you passing NSKeyedArchiver an object that does not conform to NSCoding protocol.
If you don't want to provide all properties in the init method you can use default values:
init(title : String? = nil, pageCount: Int? = nil){
self.title = title
self.pageCount = pageCount
}
Now you can just init your object without any properties. Like that Book()
Here is the solutions, you have to implement the two methods
Encode Method
func encode(with aCoder: NSCoder)
Decoding method
required init?(coder aDecoder: NSCoder)
Complete Example code
class User: NSObject , NSCoding
{
var userID : Int = 0
var name : String = ""
var firstName : String = ""
var lastName : String = ""
var username : String = ""
var email : String = ""
override init(){
super.init();
}
func encode(with aCoder: NSCoder) {
aCoder.encode(self.userID, forKey: "id");
aCoder.encode(self.firstName, forKey: "first_name");
aCoder.encode(self.lastName, forKey: "last_name");
aCoder.encode(self.name, forKey: "name");
aCoder.encode(self.username,forKey: "username");
aCoder.encode(self.email, forKey: "email");
}
required init?(coder aDecoder: NSCoder) {
super.init()
self.userID = aDecoder.decodeInteger(forKey: "id");
self.firstName = aDecoder.decodeObject(forKey: "first_name") as! String;
self.lastName = aDecoder.decodeObject(forKey: "last_name") as! String;
self.name = aDecoder.decodeObject(forKey: "name") as! String
self.username = aDecoder.decodeObject(forKey: "username") as! String
self.email = aDecoder.decodeObject(forKey: "email") as! String;
}
init(data : [String: AnyObject]) {
super.init()
self.userID = String.numberValue(data["user_id"]).intValue;
self.firstName = String.stringValue(data["first_name"]);
self.lastName = String.stringValue(data["last_name"]);
self.email = String.stringValue(data["email"]);
self.username = String.stringValue(data["user_name"]);
}
class func loadLoggedInUser() -> User {
if let archivedObject = UserDefaults.standard.object(forKey:"CurrentUserAcc"){
if let user = NSKeyedUnarchiver.unarchiveObject(with: (archivedObject as! NSData) as Data) as? User {
return user;
}
}
return User()
}
func saveUser(){
let archivedObject : NSData = NSKeyedArchiver.archivedData(withRootObject: self) as NSData
UserDefaults.standard.set(archivedObject, forKey: "CurrentUserAcc");
UserDefaults.standard.synchronize();
}
func deleteUser(){
UserDefaults.standard.set(nil, forKey: "CurrentUserAcc")
UserDefaults.standard.synchronize();
}
}
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
}
}
Current Format
Optional(posterPrint.Frame(name: "RIBBA", productNumber: "303.016.24", productSize: (63.0, 93.0), pictureSize: (61.0, 91.0), pictureWithMatSize: (50.0, 70.0), frameColor: [UIDeviceRGBColorSpace 0.92549 0.92549 0.92549 1], matColor: [UIDeviceRGBColorSpace 0.92549 0.92549 0.92549 1]))
I want to add this one to array and convert any object. I tried a lot in this to stored in NSkeyedArchiver. I read somewhere tuples are not possible to store in NSKeyedArchiever. Anyone help in this
Thanks in advance .
To use NSKeyedArchiver, you make your object conform to NSCoding. And if you have tuples, you can encode/decode the elements separately:
class MyObject: NSObject, NSCoding {
var id: Int
var size: (Float, Float)
init(id: Int, size: (Float, Float)) {
self.id = id
self.size = size
super.init()
}
required init?(coder aDecoder: NSCoder) {
guard let id = aDecoder.decodeObjectForKey("id") as? Int else { return nil }
guard let size0 = aDecoder.decodeObjectForKey("size.0") as? Float else { return nil }
guard let size1 = aDecoder.decodeObjectForKey("size.1") as? Float else { return nil }
self.id = id
self.size = (size0, size1)
super.init()
}
func encodeWithCoder(aCoder: NSCoder) {
aCoder.encodeObject(id, forKey: "id")
aCoder.encodeObject(size.0, forKey: "size.0")
aCoder.encodeObject(size.1, forKey: "size.1")
}
}
You mention arrays, so I'm not sure if you're talking about an array of your custom objects, but now that your object conforms to NSCoding, you can now archive and unarchive arrays of these objects with no further effort.
Likewise, if you have some other object that has a MyObject property, you can apply NSCoding to that, and it can take advantage of the fact that MyObject already conforms to NSCoding:
class ParentObject: NSObject, NSCoding {
var name: String
var value: Int
var child: MyObject
init(name: String, value: Int, child: MyObject) {
self.name = name
self.value = value
self.child = child
super.init()
}
required init?(coder aDecoder: NSCoder) {
name = aDecoder.decodeObjectForKey("name") as! String
value = aDecoder.decodeObjectForKey("value") as! Int
child = aDecoder.decodeObjectForKey("child") as! MyObject
super.init()
}
func encodeWithCoder(aCoder: NSCoder) {
aCoder.encodeObject(name, forKey: "name")
aCoder.encodeObject(value, forKey: "value")
aCoder.encodeObject(child, forKey: "child")
}
}
I'm trying to use NSKeyedArchiver to store custom class instance.
class feedBack: NSObject, NSCoding {
var choiceA = 0
var choiceB = 0
var choiceC = 0
var choiceD = 0
var choiceNULL = 0
var sheetName = ""
func encodeWithCoder(aCoder: NSCoder) {
aCoder.encodeObject(self.choiceA, forKey: "choiceA")
aCoder.encodeObject(self.choiceB, forKey: "choiceB")
aCoder.encodeObject(self.choiceC, forKey: "choiceC")
aCoder.encodeObject(self.choiceD, forKey: "choiceD")
aCoder.encodeObject(self.choiceNULL, forKey: "choiceNULL")
aCoder.encodeObject(self.sheetName, forKey: "sheetName")
}
required init(coder aDecoder: NSCoder) {
self.choiceA = aDecoder.decodeObjectForKey("choiceA") as! Int
self.choiceB = aDecoder.decodeObjectForKey("choiceB") as! Int
self.choiceC = aDecoder.decodeObjectForKey("choiceC") as! Int
self.choiceD = aDecoder.decodeObjectForKey("choiceD") as! Int
self.choiceNULL = aDecoder.decodeObjectForKey("choiceNULL") as! Int
self.sheetName = aDecoder.decodeObjectForKey("sheetName") as! String
}
}
Before feedBack conforms to NSCoding, I use var fb = feedBack() to create a new instance of feedBack.
Now the compiler throws Missing argument for parameter coder in call error.
Since the initWithCoder is required, how do I call the previous initializer with no parameter?
Just override init() and that should do it.
class feedBack: NSObject, NSCoding {
var choiceA = 0
var choiceB = 0
var choiceC = 0
var choiceD = 0
var choiceNULL = 0
var sheetName = ""
func encodeWithCoder(aCoder: NSCoder) {
aCoder.encodeObject(self.choiceA, forKey: "choiceA")
aCoder.encodeObject(self.choiceB, forKey: "choiceB")
aCoder.encodeObject(self.choiceC, forKey: "choiceC")
aCoder.encodeObject(self.choiceD, forKey: "choiceD")
aCoder.encodeObject(self.choiceNULL, forKey: "choiceNULL")
aCoder.encodeObject(self.sheetName, forKey: "sheetName")
}
required init(coder aDecoder: NSCoder) {
self.choiceA = aDecoder.decodeObjectForKey("choiceA") as! Int
self.choiceB = aDecoder.decodeObjectForKey("choiceB") as! Int
self.choiceC = aDecoder.decodeObjectForKey("choiceC") as! Int
self.choiceD = aDecoder.decodeObjectForKey("choiceD") as! Int
self.choiceNULL = aDecoder.decodeObjectForKey("choiceNULL") as! Int
self.sheetName = aDecoder.decodeObjectForKey("sheetName") as! String
}
override init(){
}
}
feedBack()