NSCoding in Swift not archiving data properly? - ios

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

Related

NSKeyedArchiver.archivedData does not work in Swift 3 iOS

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();
}
}

Could not use lazy variable inside NSCoding protocol methods

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]
}

ios swift, How to convert 'object to nsdata'

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???")
}

-[_SwiftValue encodeWithCoder:]: unrecognized selector sent to instance

Getting error when trying to utilize NSCoder
Player.swift:
class Player: NSObject, NSCoding {
private var _playerName: String!
private var _playerScore: Int!
private var _playerColor: PlayerColor! //PlayerColor is an enum
var playerName: String {
get {
return _playerName
}
set {
_playerName = newValue
}
}
var playerScore: Int {
get {
return _playerScore
}
set {
_playerScore = newValue
}
}
var playerColor: PlayerColor {
get {
return _playerColor
}
set {
_playerColor = newValue
}
}
init(playerName: String, playerScore: Int, playerColor: PlayerColor) {
_playerName = playerName
_playerScore = playerScore
_playerColor = playerColor
}
required convenience init(coder aDecoder: NSCoder) {
let name = aDecoder.decodeObject(forKey: "name") as! String
let score = aDecoder.decodeInteger(forKey: "score")
let color = aDecoder.decodeObject(forKey: "color") as! PlayerColor
self.init(playerName: name, playerScore: score, playerColor: color)
}
func encode(with aCoder: NSCoder){
aCoder.encode(playerName, forKey: "name")
aCoder.encode(playerScore, forKey: "score")
aCoder.encode(playerColor, forKey: "color")
}
}
in PlayerStore.swift:
// Storage Functions
func savePlayers(){
let encodedData = NSKeyedArchiver.archivedData(withRootObject: _playerArray) // _playerarray is a [Player] the very object I want to store/retrieve at will
defaults.set(encodedData, forKey: playerKeyForDefaults) //defaults is just var NSUserDefaults.standard
defaults.synchronize()
}
func loadPlayers(){
if let decoded = defaults.object(forKey: playerKeyForDefaults) as? NSData {
let array = NSKeyedUnarchiver.unarchiveObject(with: decoded as Data) as! [Player]
_playerArray = array
}
}
Here's the solution I implemented:
Player.swift:
import Foundation
class Player: NSObject, NSCoding {
private var name: String!
private var score: Int!
private var color: String!
var playerName: String {
get {
return name
}
set {
name = newValue
}
}
var playerScore: Int {
get {
return score
}
set {
score = newValue
}
}
var playerColor: String {
get {
return color
}
set {
color = newValue
}
}
init(playerName: String, playerScore: Int, playerColor: String) {
name = playerName
score = playerScore
color = playerColor
}
required convenience init(coder aDecoder: NSCoder) {
let name = aDecoder.decodeObject(forKey: "name") as! String
let score = aDecoder.decodeObject(forKey: "score") as! Int
let color = aDecoder.decodeObject(forKey: "color") as! String
self.init(playerName: name, playerScore: score, playerColor: color)
}
func encode(with aCoder: NSCoder){
aCoder.encode(name, forKey: "name")
aCoder.encode(score, forKey: "score")
aCoder.encode(color, forKey: "color")
}
}
PlayerStore.swift:
func savePlayers(){
let encodedData = NSKeyedArchiver.archivedData(withRootObject: _playerArray)
defaults.set(encodedData, forKey: playerKeyForDefaults)
}
func loadPlayers(){
if let decoded = defaults.object(forKey: playerKeyForDefaults) as? NSData {
let array = NSKeyedUnarchiver.unarchiveObject(with: decoded as Data) as! [Player]
_playerArray = array
}
}
You can also use this approach using Enum rawValue. It might help you to archivedData and unarchiveObject a complete model object.
Color Enum With Hex value:
enum PlayerColor: String {
case red = "#FF0000"
case silver = "#C0C0C0"
case gray = "#808080"
case black = "#000000"
var description: String {
return self.rawValue
}
}
import Foundation
class Player: NSObject, NSCoding {
private var name: String!
private var score: Int!
private var color: PlayerColor!
init(playerName: String, playerScore: Int, playerColor: PlayerColor) {
name = playerName
score = playerScore
color = playerColor
}
required convenience init(coder aDecoder: NSCoder) {
let name = aDecoder.decodeObject(forKey: "name") as! String
let score = aDecoder.decodeObject(forKey: "score") as! Int
if let value = aDecoder.decodeObject(forKey: "color") as? String{
color = PlayerColor(rawValue: value)
}
self.init(playerName: name, playerScore: score, playerColor: color)
}
func encode(with aCoder: NSCoder){
aCoder.encode(name, forKey: "name")
aCoder.encode(score, forKey: "score")
let value = color!.description
aCoder.encode(value, forKey: "color")
}
}

Class isn't conforming to NSCoding protocol

I've been using apple's guide for saving data in swift, but when I try to compile it's telling me that is isn't conforming. Based on apple's guide and other code it should be.
// MARK: NSCoding
func encodeWithCoder(aCoder: NSCoder) {
aCoder.encodeObject(firstName, forKey: "firstName")
aCoder.encodeObject(lastName, forKey: "lastName")
aCoder.encodeObject(phoneNumber, forKey: "phoneNumber")
aCoder.encodeObject(email, forKey: "email")
aCoder.encodeObject(address, forKey: "address")
}
required convenience init?(coder aDecoder: NSCoder) {
firstName = aDecoder.decodeObjectForKey("firstName") as! String
lastName = aDecoder.decodeObjectForKey("lastName") as! String
phoneNumber = aDecoder.decodeObjectForKey("phoneNumber") as! String
email = aDecoder.decodeObjectForKey("email") as! String
address = aDecoder.decodeObjectForKey("addressq") as! String
self.init(phoneNumber: phoneNumber, firstName: firstName, lastName: lastName, email: email, address: address)
}
So I'm simply not sure why it isn't conforming when I can run Apple's program fine.
EDIT: So the problem seems to be that I didn't have my class inherited from NSObject. That solved the problem.
No errors here:
class Dog {
var firstName: String
func encodeWithCoder(aCoder: NSCoder) {
aCoder.encodeObject(firstName, forKey: "firstName")
}
init(firstName: String) {
self.firstName = firstName
}
required convenience init?(coder aDecoder: NSCoder) {
let x = aDecoder.decodeObjectForKey("firstName") as! String
self.init(firstName: x)
}
}
var d = Dog(firstName: "Rover")

Resources