Getting optional String from singleton - swift

I've created a class with some vars and lets. One of these vars is a String. I store them in UserDefaults. If I want to access the string of this class over a singleton class, I will always get an optional String. I don't know why.
Here is the class of the object:
import Foundation
import SpriteKit
class BallSkinsClass: NSObject, NSCoding {
let id: Int
var name: String
var isBuyed: Bool
let ID = "id"
let NAME = "name"
let ISBUYED = "isBuyed"
init(id: Int, name: String, isBuyed: Bool) {
self.id = id
self.name = name
self.isBuyed = isBuyed
}
required init?(coder aDecoder: NSCoder) {
self.id = aDecoder.decodeInteger(forKey: ID)
self.name = String(describing: aDecoder.decodeObject(forKey: NAME))
self.isBuyed = aDecoder.decodeBool(forKey: ISBUYED)
}
#objc func encode(with aCoder: NSCoder) {
aCoder.encode(id, forKey: ID)
aCoder.encode(name, forKey: NAME)
aCoder.encode(isBuyed, forKey: ISBUYED)
}
}
To declare the skins, access, save and load I have these functions in my BallSkinsClass:
import Foundation
import SpriteKit
import GameKit
class BallSkins {
static var sharedInstance = BallSkins()
private init() {
}
let BALLSKINS = "ballSkins"
var standard: BallSkinsClass! = BallSkinsClass(id: 0, name: "Standard", isBuyed: true)
var billiard: BallSkinsClass! = BallSkinsClass(id: 1, name: "Billard", isBuyed: false)
var emoji: BallSkinsClass! = BallSkinsClass(id: 2, name: "Emojis", isBuyed: false)
func archiveBallSkins(ballSkins:[BallSkinsClass]) -> NSData {
print("archiving Skins")
let archivedBallSkins = NSKeyedArchiver.archivedData(withRootObject: ballSkins as Array)
return archivedBallSkins as NSData
}
func saveBallSkins(ballSkins:[BallSkinsClass]) {
let archivedBallSkins = archiveBallSkins(ballSkins: ballSkins)
UserDefaults.standard.set(archivedBallSkins, forKey: BALLSKINS)
print("saving Skins")
}
func retrieveBallSkins() -> [BallSkinsClass]? {
print("retrieving Skins")
if let unarchivedBallSkins = UserDefaults.standard.object(forKey: BALLSKINS) as? NSData {
return NSKeyedUnarchiver.unarchiveObject(with: unarchivedBallSkins as Data) as? [BallSkinsClass]
}
return nil
}
func loadBallSkins() {
print("loading Skins")
let archivedBallSkins = retrieveBallSkins()
for ballSkin in archivedBallSkins! {
switch ballSkin.id {
case 0 :
standard.isBuyed = ballSkin.isBuyed
case 1:
billiard.isBuyed = ballSkin.isBuyed
case 2:
emoji.isBuyed = ballSkin.isBuyed
default:
standard.isBuyed = ballSkin.isBuyed
}
}
}
}
If I want to access the name of the skin in any other scene or view I call:
ballSkins.sharedInstance.billiard.name
But this is an optional every time! I don't know why or where the error is.

I suppose it is caused by
required init?(coder aDecoder: NSCoder) {
self.id = aDecoder.decodeInteger(forKey: ID)
self.name = String(describing: aDecoder.decodeObject(forKey: NAME))
self.isBuyed = aDecoder.decodeBool(forKey: ISBUYED)
}
3rd line generates optional string because according to documentation
func decodeObject() -> Any?
and String(describing: ...) does not unwrap your value. You must unwrap all values from UserDefaults by yourself, providing defaultValue if nil is not possible

Related

Swift NSSecureCoding unexpectedly found nil while unwrapping

I am storing a transformable in CoreData and am trying to make it NSSecureCoding compliant, but I keep getting Unexpectedly found nil whilst unwrapping an optional value, when I try to decode
in the XCDataModel, I am set the property as transformable and use the custom class AssignedExerciseObjects, I have also set the transformer to AssignedExerciseObjectsValueTransformer
I have also registered the transformer in my CoreData stack
here is a copy of my transformer
#objc(AssignedExerciseObjectsValueTransformer)
final class AssignedExerciseObjectsValueTransformer: NSSecureUnarchiveFromDataTransformer {
static let name = NSValueTransformerName(rawValue: String(describing: AssignedExerciseObjectsValueTransformer.self))
override static var allowedTopLevelClasses: [AnyClass] {
return [NSArray.self, AssignedExerciseObjects.self]
}
/// Registers the transformer.
public static func register() {
let transformer = AssignedExerciseObjectsValueTransformer()
ValueTransformer.setValueTransformer(transformer, forName: name)
}
}
and here is a copy of my custom class
import Foundation
public class AssignedExerciseObjects: NSObject, NSSecureCoding {
public static var supportsSecureCoding: Bool = true
public var assignedExerciseObjects: [AssignedExerciseObject] = []
enum Key:String {
case assignedExerciseObjects = "assignedExerciseObjects"
}
init(assignedExerciseObjects: [AssignedExerciseObject]) {
self.assignedExerciseObjects = assignedExerciseObjects
}
public func encode(with aCoder: NSCoder) {
aCoder.encode(assignedExerciseObjects, forKey: Key.assignedExerciseObjects.rawValue)
}
public required convenience init?(coder aDecoder: NSCoder) {
let mAssignedExerciseObjects = aDecoder.decodeObject(of: NSArray.self, forKey: Key.assignedExerciseObjects.rawValue) as! [AssignedExerciseObject]
self.init(assignedExerciseObjects: mAssignedExerciseObjects)
}
}
public class AssignedExerciseObject: NSObject, NSSecureCoding {
public static var supportsSecureCoding: Bool = true
public var mainExercise: String = ""
public var prepareTime: String = ""
public var exerciseTime: String = ""
public var highIntensityTime: String = ""
public var restTime: String = ""
public var highLowSplit: Bool = false
public var isCompleted: Bool = false
public var isSkipped: Bool = false
public var order: Double = 0
enum Key:String {
case mainExercise = "mainExercise"
case prepareTime = "prepareTime"
case exerciseTime = "exerciseTime"
case highIntensityTime = "highIntensityTime"
case restTime = "restTime"
case highLowSplit = "highLowSplit"
case isCompleted = "isCompleted"
case isSkipped = "isSkipped"
case order = "order"
}
init(mainExercise: String, prepareTime: String, exerciseTime: String, highIntensityTime: String, restTime: String, highLowSplit: Bool, isCompleted: Bool, isSkipped: Bool, order: Double) {
self.mainExercise = mainExercise
self.prepareTime = prepareTime
self.exerciseTime = exerciseTime
self.highIntensityTime = highIntensityTime
self.restTime = restTime
self.highLowSplit = highLowSplit
self.isCompleted = isCompleted
self.isSkipped = isSkipped
self.order = order
}
public override init() {
super.init()
}
public func encode(with aCoder: NSCoder) {
aCoder.encode(mainExercise, forKey: Key.mainExercise.rawValue)
aCoder.encode(prepareTime, forKey: Key.prepareTime.rawValue)
aCoder.encode(exerciseTime, forKey: Key.exerciseTime.rawValue)
aCoder.encode(highIntensityTime, forKey: Key.highIntensityTime.rawValue)
aCoder.encode(restTime, forKey: Key.restTime.rawValue)
aCoder.encode(highLowSplit, forKey: Key.highLowSplit.rawValue)
aCoder.encode(isCompleted, forKey: Key.isCompleted.rawValue)
aCoder.encode(isSkipped, forKey: Key.isSkipped.rawValue)
aCoder.encode(order, forKey: Key.order.rawValue)
}
public required convenience init?(coder aDecoder: NSCoder) {
let mMainExercise = aDecoder.decodeObject(of: NSString.self, forKey: Key.mainExercise.rawValue)! as String
let mPrepareTime = aDecoder.decodeObject(of: NSString.self,forKey: Key.prepareTime.rawValue)! as String
let mExerciseTime = aDecoder.decodeObject(of: NSString.self,forKey: Key.exerciseTime.rawValue)! as String
let mHighIntensityTime = aDecoder.decodeObject(of: NSString.self,forKey: Key.highIntensityTime.rawValue)! as String
let mRestTime = aDecoder.decodeObject(of: NSString.self,forKey: Key.restTime.rawValue)! as String
let mHighLowSplit = aDecoder.decodeBool(forKey: Key.highLowSplit.rawValue)
let mIsCompleted = aDecoder.decodeBool(forKey: Key.isCompleted.rawValue)
let mIsSkipped = aDecoder.decodeBool(forKey: Key.isSkipped.rawValue)
let mOrder = aDecoder.decodeDouble(forKey: Key.order.rawValue)
self.init(mainExercise: String(mMainExercise), prepareTime:
String(mPrepareTime), exerciseTime: String(mExerciseTime), highIntensityTime: String(mHighIntensityTime), restTime: String(mRestTime), highLowSplit: Bool(mHighLowSplit), isCompleted: Bool(mIsCompleted), isSkipped: Bool(mIsSkipped), order: Double(mOrder))
}
}
this is where it gives me the error
let mAssignedExerciseObjects = aDecoder.decodeObject(of: NSArray.self, forKey: Key.assignedExerciseObjects.rawValue) as! [AssignedExerciseObject]
I'm still fairly new to swift, but I cannot for the life of me work out why it is returning a nil value?
when the record is created, it all seems to work fine, but when I try to display the record in a table, I get the error
any help is greatly appreciated
regards
Jamie
OK, I've fixed it
Adding my custom class fixed the issue
let mAssignedExerciseObjects = aDecoder.decodeObject(of: [AssignedExerciseObject.self, NSArray.self], forKey: Key.assignedExerciseObjects.rawValue) as! [AssignedExerciseObject]

Save Enums with NSUserDefaults, unrecognized selector

I Have this enums:
#objc enum Groups: Int {
case large = 0
case medium = 1
case small = 2
case micro = 3
case all = 4
case favoriten = 5
}
enum Ordering:String {
case ascending
case descending
case nothing
}
enum QuantityStats:String {
case one
case two
case three
case four
case six
}
enum KeysState: String {
case listTableViewState
case listLabelState
}
These are part of this class:
class ListTableViewState: State, NSCoding {
private enum Keys: String {
case group
case statIntervalBase
case order
case quantityStats
}
var group: Groups {
didSet {
if initialized {
saveUserDefaults(withKey: KeysState.listTableViewState.rawValue, myType: self)
}
}
}
var statIntervalBase: StatIntervalBaseModel
var order: Ordering
var searchParameter: String?
var quantityStats: QuantityStats
init(group: Groups, statIntervalBase: StatIntervalBaseModel, order: Ordering, searchParameter: String?, quantityStats: QuantityStats) {
self.group = group
self.statIntervalBase = statIntervalBase
self.order = order
self.searchParameter = searchParameter
self.quantityStats = quantityStats
print("Group", Keys.group, "order", Keys.order)
super.init()
initialized = true
}
required convenience init?(coder aDecoder: NSCoder) {
// self.stage = Stage(rawValue: aDecoder.decodeObject(forKey: "stage") as String)
let group = Groups(rawValue: aDecoder.decodeObject(forKey: Keys.group.rawValue) as! Int)
let statIntervalBase = aDecoder.decodeObject(forKey: Keys.statIntervalBase.rawValue) as! StatIntervalBaseModel
let order = Ordering(rawValue: aDecoder.decodeObject(forKey: Keys.order.rawValue) as! String)
let quantityStats = QuantityStats(rawValue: aDecoder.decodeObject(forKey: Keys.quantityStats.rawValue) as! String)
self.init(group: group!, statIntervalBase: statIntervalBase, order: order!, searchParameter: "", quantityStats: quantityStats!)
}
func encode(with aCoder: NSCoder) {
aCoder.encode(group, forKey: Keys.group.rawValue)
aCoder.encode(statIntervalBase, forKey: Keys.statIntervalBase.rawValue)
aCoder.encode(order, forKey: Keys.order.rawValue)
aCoder.encode(quantityStats, forKey: Keys.quantityStats.rawValue)
}
}
This is the State Base class I made:
class State: NSObject {
var initialized = false
func saveUserDefaults<T>(withKey key: String, myType: T){
let archiver:Data = NSKeyedArchiver.archivedData(withRootObject: myType)
UserDefaults.standard.set(archiver, forKey: key)
UserDefaults.standard.synchronize()
}
func loadUserDefaults<T>(withKey key: String) -> T? {
var output: T?
let decoded = UserDefaults.standard.object(forKey: key)
if decoded != nil {
let data = decoded as! Data
output = (NSKeyedUnarchiver.unarchiveObject(with: data) as! T)
} else {
output = nil
}
return output
}
}
I want to save ListTableViewState with NSUserDefaults but I keep getting errors, I think I am saving the enums wrong. But I could not figure it out how to solve it.
This is the error I get:
2018-10-29 18:53:22.906915+0000 APP[40545:8188629] -[_SwiftValue
encodeWithCoder:]: unrecognized selector sent to instance
0x600001c7d7a0 2018-10-29 18:53:22.912060+0000 APP[40545:8188629]
* Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[_SwiftValue
encodeWithCoder:]: unrecognized selector sent to instance
0x600001c7d7a0'
* First throw call stack:
You construct each enum value from rawValue in your init(coder:). Why don't you encode rawValue?
required convenience init?(coder aDecoder: NSCoder) {
let group = Groups(rawValue: aDecoder.decodeInteger(forKey: Keys.group.rawValue))
let statIntervalBase = aDecoder.decodeObject(forKey: Keys.statIntervalBase.rawValue) as! StatIntervalBaseModel
let order = Ordering(rawValue: aDecoder.decodeObject(forKey: Keys.order.rawValue) as! String)
let quantityStats = QuantityStats(rawValue: aDecoder.decodeObject(forKey: Keys.quantityStats.rawValue) as! String)
self.init(group: group!, statIntervalBase: statIntervalBase, order: order!, searchParameter: "", quantityStats: quantityStats!)
}
func encode(with aCoder: NSCoder) {
aCoder.encode(group.rawValue, forKey: Keys.group.rawValue)
aCoder.encode(statIntervalBase, forKey: Keys.statIntervalBase.rawValue)
aCoder.encode(order.rawValue, forKey: Keys.order.rawValue)
aCoder.encode(quantityStats.rawValue, forKey: Keys.quantityStats.rawValue)
}
And I think you can write loadUserDefaults(withKey:) as:
func loadUserDefaults<T>(withKey key: String) -> T? {
var output: T? = nil
if let data = UserDefaults.standard.data(forKey: key) {
output = (NSKeyedUnarchiver.unarchiveObject(with: data) as! T)
}
return output
}
(Shouldn't this be static?)

Custom Class Unarchive is nil in Cocoa Swift

I am trying to save and retrieve a custom class to UserDefaults in my macOS app. I am getting nil for newData
class countClass: NSObject, NSCoding {
var leftClickCount : Int = 0
init(leftClickCount: Int) {
self.leftClickCount = leftClickCount
super.init()
}
func encode(with coder: NSCoder) {
coder.encode(self.leftClickCount, forKey: "leftClickCount")
}
required convenience init?(coder decoder: NSCoder) {
guard let leftClickCount = decoder.decodeObject(forKey: "leftClickCount") as? Int
else {
return nil
}
self.init(
leftClickCount: leftClickCount
)
}
}
class ViewController: NSViewController {
override func viewDidLoad() {
super.viewDidLoad()
let leftC = countClass(leftClickCount: 25)
let ud = UserDefaults.standard
let archivedData = NSKeyedArchiver.archivedData(withRootObject: leftC)
ud.set(archivedData, forKey: "data")
ud.synchronize()
let tempData = ud.object(forKey: "data") as! Data
let newData = NSKeyedUnarchiver.unarchiveObject(with: tempData) as! countClass // Getting nil here
}
}
I was able to fix this problem by changing from:
decoder.decodeObject(forKey: "leftClickCount") as? Int
with:
decoder.decodeInteger(forKey: "leftClickCount")

Why is retrieval from NSUserDefaults failing for my custom object?

I have a class PredicPair which inherits from NSCoding and NSObject as such:
class PredicPair: NSObject, NSCoding {
var weight : Float
var prediction : String
init(weight: Float, prediction: String) {
self.weight = weight
self.prediction = prediction
super.init()
}
func encode(with aCoder: NSCoder) {
aCoder.encode(weight, forKey: "weight")
aCoder.encode(prediction, forKey: "prediction")
}
required convenience init(coder aDecoder: NSCoder) {
let unarchivedWeight = aDecoder.decodeObject(forKey: "weight") as! Float
let unarchivedPrediction = aDecoder.decodeObject(forKey: "prediction") as! String
self.init(weight: unarchivedWeight, prediction: unarchivedPrediction)
}
class func saveToUserDefaults(pairs: [PredicPair]) {
let dataBlob = NSKeyedArchiver.archivedData(withRootObject: pairs)
UserDefaults.standard.set(dataBlob, forKey: "test")
UserDefaults.standard.synchronize()
}
class func loadFromUserDefaults() -> [PredicPair]? {
guard let decodedNSDataBlob = UserDefaults.standard.object(forKey: "test") as? NSData,
let loadedFromUserDefault = NSKeyedUnarchiver.unarchiveObject(with: decodedNSDataBlob as Data) as? [PredicPair]
else {
return nil
}
return loadedFromUserDefault
}
}
I am trying to store an array of the class in UserDefaults and retrieving it, but the latter always returns nil. Any reason why?
let predicPair1 = PredicPair(weight: 0, prediction: "there")
let predicPair2 = PredicPair(weight: 1, prediction: "hello")
let array = [predicPair1, predicPair2]
PredicPair.saveToUserDefaults(pairs: array)
if let retreivedArray = PredicPair.loadFromUserDefaults() {
print("loaded \(retreivedArray.count) players from NSUserDefaults")
} else {
print("failed")
}
I've also tried saving to a file using NSKeyedArchiver but retrieval fails as well.

Unarchiving a dictionary with unarchiveObjectWithFile using swift

I am facing problem with the data that I got from using unarchiveObjectWithFile. I send a dictionary for archiving. The data, I got shows the value while debugging but I can't extract the exact value for key from the data. Always getting nil value
Here is my code
class Person : NSObject, NSCoding {
struct Keys {
static let Name = "name"
static let Age = "age"
}
var name: [String] = []
var age: [Int] = []
init(dictionary: [String : AnyObject]) {
name = dictionary[Keys.Name] as! [String]
age = dictionary[Keys.Age] as! [Int]
}
func encodeWithCoder(archiver: NSCoder) {
archiver.encodeObject(name, forKey: Keys.Name)
archiver.encodeObject(age, forKey: Keys.Age)
}
required init(coder unarchiver: NSCoder) {
if let namesList = unarchiver.decodeObjectForKey(Keys.Name) as? [String] {
name = namesList
print("name\(name)")
}
if let agesList = unarchiver.decodeObjectForKey(Keys.Age) as? [Int] {
age = agesList
print("age \(age)")
}
super.init()
}
}
// View controller
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let persons: [String:AnyObject] = [
"name" : ["a","b","c"],
"age" : [2,3,4]
]
let personObj = Person(dictionary: persons)
NSKeyedArchiver.archiveRootObject(personObj, toFile: "/Users/shuvo/Desktop/Data.json")
let responseObject = NSKeyedUnarchiver.unarchiveObjectWithFile("/Users/shuvo/Desktop/Data.json") as? Person
if let unwrapData: AnyObject = responseObject {
let nameArr = unwrapData["name"] as? [String]
let ageArr = unwrapData["age"] as? [Int]
print("age \(ageArr) name \(nameArr)")
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
That's because you are archiving the Person object and not the [String: AnyObject].
You can either archive the Person, and extract it as a Person or archive the [String: AnyObject] and extract that. You can't use both as they are different types.
If you want to archive the Person object, make sure you implement the NSCoding protocol and have something similar to this in your Person class...
func encode(with aCoder: NSCoder) {
aCoder.encode(name, forKey: "name")
aCoder.encode(age, forKey: "age")
}
required init?(coder aDecoder: NSCoder) {
self.name = aDecoder.decodeObject(forKey: "name") as! [String]
self.age = aDecoder.decodeObject(forKey: "age") as! [Int]
}
After that, you can just do...
if let responseObject = NSKeyedUnarchiver.unarchiveObject(withFile: path) as? Person {
// You can use `responseObject` as a `Person` object here...
}