How to initialize a custom class instance that conforms to NSCoding? - ios

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

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

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

Swift NSObject subclass get BAD_ACCESS

I try to persiste my object with NSCoding but i always get BAD_ACCESS ERROR To avoid multi multiple like variable, class, i put all common variable in RObject. I think i do something wrong the the init but i don't know what.
the error was thow in this function
func parseInfo(allInfos : String) -> Void {
if let all : JSON = JSON.parse(allInfos) as JSON? {
if let info = all.asArray
{
for description in info
{
var track : RInfo = SCTracks(js: description)
self.arrayTracks.addObject(track)
} // Therad 1: EXC_BAD_ACCESS(code=2, address=0x27...)
}
}
}
The Log doesn't show any thing
My Common Class
class RObject : NSObject, NSCoding {
var id : Int? = 0
var kind : String?
override init() { super.init() }
init(js :JSON) {
self.kind = js["kind"].asString
self.id = js["id"].asInt
super.init()
}
required
init(coder aDecoder: NSCoder) {
self.id = aDecoder.decodeIntegerForKey("id") as Int
self.kind = aDecoder.decodeObjectForKey("kind") as? String
}
func encodeWithCoder(aCoder: NSCoder) {
aCoder.encodeInteger(self.id!, forKey: "id")
aCoder.encodeObject(self.kind, forKey: "kind")
}
}
My class Rinfo who inherits from RObject
class RInfo : RObject {
var title :String?
var uri :String?
var license :String?
var release :String?
var user :RUser!
override init() { super.init() }
required init(coder: NSCoder) {
self.title = coder.decodeObjectForKey("title") as? String
self.user = coder.decodeObjectForKey("user") as RUser
self.license = coder.decodeObjectForKey("license") as? String
self.uri = coder.decodeObjectForKey("uri") as? String
self.release = coder.decodeObjectForKey("release") as? String
super.init(coder: coder)
}
init(js :JSON) {
self.user = js(js: js["user"])
self.title = js["title"].asString
self.license = js["license"].asString
self.uri = js["uri"].asString
self.release = js["release"].asString
super.init(js: js)
}
override func encodeWithCoder(encoder: NSCoder) {
encoder.encodeObject(self.title, forKey: "title")
encoder.encodeObject(self.user, forKey: "user")
encoder.encodeObject(self.uri, forKey: "uri")
encoder.encodeObject(self.license, forKey: "license")
}
}
Thanks for any help !
I Solve my problem by remove variable release in RInfo. that strange

NSUserDefaults Custom object - Property list invalid for format: 200 (property lists cannot contain objects of type 'CFType')

I must be incorrectly using Custom Objects for NSUserDefaults. The error " Property list invalid for format: 200 (property lists cannot contain objects of type 'CFType')". Below is my code, the Goal class is of particular interest, since this is where I am adopting the NSCoding protocol.
This code is global.
func saveGoals (goals : [Goal]) {
var updatedGoals = NSKeyedArchiver.archivedDataWithRootObject(goals)
NSUserDefaults.standardUserDefaults().setObject(updatedGoals, forKey: "Goals")
NSUserDefaults.standardUserDefaults().synchronize()
}
func loadCustomObjectWithKey() -> [Goal] {
if let encodedObject : NSData = NSUserDefaults.standardUserDefaults().objectForKey("Goals") as? NSData {
var encodedObject : NSData? = NSUserDefaults.standardUserDefaults().objectForKey("Goals") as? NSData
var goal : [Goal] = NSKeyedUnarchiver.unarchiveObjectWithData(encodedObject!) as [Goal]
return goal
} else {
return [Goal]()
}
}
This code is in GoalsViewController.
class GoalsViewController: MainPageContentViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet var tableView: GoalsTableView!
var cell = GoalTableViewCell()
var goalsArray : Array<Goal> = [] //
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.delegate = self
self.tableView.dataSource = self
if var storedGoals: [Goal] = loadCustomObjectWithKey() as [Goal]? {
goalsArray = storedGoals
}
//retrieve data.
var goal = Goal(title: "Walk the Dog")
goalsArray.append(goal)
saveGoals(goalsArray)
self.tableView?.reloadData()
tableView.estimatedRowHeight = 44.0
tableView.rowHeight = UITableViewAutomaticDimension
var notification = NSNotificationCenter.defaultCenter()
notification.addObserver(self, selector: "finishCreatingGoal:", name: "FinishCreatingGoal", object: nil)
}
func finishCreatingGoal(notification : NSNotification) {
if (notification.name == "FinishCreatingGoal") {
var userInfo = notification.userInfo!
var text = userInfo["text"]! as String
var index = userInfo["index"]! as Int
var cell = self.tableView.cellForRowAtIndexPath(NSIndexPath(forRow: index, inSection: 0)) as GoalTableViewCell
goalsArray[index].title = cell.goalTextField.text
saveGoalList(goalsArray)
self.tableView.reloadData()
self.tableView.scrollToRowAtIndexPath(NSIndexPath(forRow: index, inSection: 0), atScrollPosition: UITableViewScrollPosition.Middle, animated: true)
}
}
This code is in the Goal class.
import UIKit
class Goal : NSObject, NSCoding {
var title : String? = ""
var checkmarked : Bool? = false
var isLastCell : Bool? = false
var enabled : Bool? = true
var priority = Priority.defaultPriority
override init() {
}
init(title : String) {
self.title = title
}
func encodeWithCoder(aCoder: NSCoder) {
aCoder.encodeObject(title!, forKey: "title")
aCoder.encodeBool(checkmarked!, forKey: "checkmarked")
aCoder.encodeBool(isLastCell!, forKey: "isLastCell")
aCoder.encodeBool(enabled!, forKey: "enabled")
}
required init(coder aDecoder: NSCoder) {
title = aDecoder.decodeObjectForKey("title") as String!
checkmarked = aDecoder.decodeBoolForKey("checkmarked") as Bool
isLastCell = aDecoder.decodeBoolForKey("isLastCell") as Bool
enabled = aDecoder.decodeBoolForKey("enabled") as Bool
}
}
I am going to just copy code from a working project I have:
here is the Game object class with data from a math flash card game:
import Foundation
class GameData: NSObject {
var sign: String = "+"
var level: Int = 1
var problems: Int = 10
var time: Int = 30
var skipWrong: Bool = true
var usedTime: Int = 0
var correctCount: Int = 0
var correctTopNumber: [Int] = [Int]()
var correctBottomNumber: [Int] = [Int]()
var wrongTopNumber: [Int] = [Int]()
var wrongBottomNumber: [Int] = [Int]()
var date: NSDate = NSDate()
func encodeWithCoder(aCoder: NSCoder!) {
aCoder.encodeObject(sign, forKey: "sign")
aCoder.encodeInteger(level, forKey: "level")
aCoder.encodeInteger(problems, forKey: "problems")
aCoder.encodeInteger(time, forKey: "time")
aCoder.encodeBool(skipWrong, forKey: "skipWrong")
aCoder.encodeInteger(usedTime, forKey: "usedTime")
aCoder.encodeInteger(correctCount, forKey: "correctCount")
aCoder.encodeObject(correctTopNumber, forKey: "correctTopNumber")
aCoder.encodeObject(correctBottomNumber, forKey: "correctBottomNumber")
aCoder.encodeObject(wrongTopNumber, forKey: "wrongTopNumber")
aCoder.encodeObject(wrongBottomNumber, forKey: "wrongBottomNumber")
aCoder.encodeObject(date, forKey: "date")
}
init(coder aDecoder: NSCoder!) {
sign = aDecoder.decodeObjectForKey("sign") as String
level = aDecoder.decodeIntegerForKey("level")
problems = aDecoder.decodeIntegerForKey("problems")
time = aDecoder.decodeIntegerForKey("time")
skipWrong = aDecoder.decodeBoolForKey("skipWrong")
usedTime = aDecoder.decodeIntegerForKey("usedTime")
correctCount = aDecoder.decodeIntegerForKey("correctCount")
correctTopNumber = aDecoder.decodeObjectForKey("correctTopNumber") as Array
correctBottomNumber = aDecoder.decodeObjectForKey("correctBottomNumber") as Array
wrongTopNumber = aDecoder.decodeObjectForKey("wrongTopNumber") as Array
wrongBottomNumber = aDecoder.decodeObjectForKey("wrongBottomNumber") as Array
date = aDecoder.decodeObjectForKey("date") as NSDate
}
override init() {
}
}
This part looks about the same as yours, but with more variable types. The archiver and retriever classes differ from you:
import Foundation
class ArchiveGameData:NSObject {
var documentDirectories:NSArray = []
var documentDirectory:String = ""
var path:String = ""
func ArchiveResults(#dataSet: [GameData]) {
documentDirectories = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)
documentDirectory = documentDirectories.objectAtIndex(0) as String
path = documentDirectory.stringByAppendingPathComponent("results3.archive")
if NSKeyedArchiver.archiveRootObject(dataSet, toFile: path) {
//println("Success writing to file!")
} else {
println("Unable to write to file!")
}
}
func RetrieveGameData() -> NSObject {
var dataToRetrieve = [GameData]()
documentDirectories = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)
documentDirectory = documentDirectories.objectAtIndex(0) as String
path = documentDirectory.stringByAppendingPathComponent("results3.archive")
if let dataToRetrieve2 = NSKeyedUnarchiver.unarchiveObjectWithFile(path) as? [GameData] {
dataToRetrieve = dataToRetrieve2
}
return(dataToRetrieve)
}
}
Finally, the code for storing and retrieving from within a ViewController:
//retrieveing
var gameDataArray = ArchiveGameData().RetrieveGameData() as [GameData]
//Archiving
gameData = GameData() //create local object then append all the new data, then store it
gameData.sign = buttonStates.sign
gameData.level = buttonStates.level
gameData.problems = buttonStates.problems
gameData.time = buttonStates.time
//etc. for all properties
ArchiveGameData().ArchiveResults(dataSet: gameDataArray)

Resources