Thread 1: EXC_BAD INSTRUCTION with NSUserDefaults - swift

I have it so that when my game ends, it switches to a separate SKScene which shows the new high score. Here is my code:
func saveState() {
let defaults = NSUserDefaults.standardUserDefaults()
defaults.setInteger(highScore, forKey: "labelScore") //this line says "unexpectedly found nil while unwrapping an optional value
defaults.setInteger(stars, forKey: "SNOW")
NSUserDefaults.standardUserDefaults().synchronize()
}
What could be nil? I have values for everything in this function. Will post more code if necessary.

This means that it was probably saved wrong to begin with. I would recommend using constants for things such as accessing user defaults so that this type of thing doesn't happen.
EDIT:
What I usually do is create a separate file that is a global constants file as part of a struct
In this file you can define constants like so:
struct GlobalConstants {
static let defaultsHighScore = "labelScore"
}
Then, when I try to read from defaults, instead of typing in everything every time, I can just use the constant like this: defaults.setInteger(highScore, forKey: GlobalConstants.defaultsHighScore)

Related

Strange static string constant behaviour in swift

I'm declaring two strings
public static let FILTER_PERIODS_KEY = "fi_pe"
public static let FILTER_PERIODS_DEFAULT = "M1,M5,M15,M30,H1,H4,D1"
and use them that way:
if (preferences.object(forKey: InAppProperties.FILTER_PERIODS_KEY) == nil) {
preferences.set(InAppProperties.FILTER_PERIODS_DEFAULT, forKey: InAppProperties.FILTER_PERIODS_KEY)
}
For some reason FILTER_PERIODS_KEY is not empty, while FILTER_PERIODS_DEFAULT is empty.
What is this?
The code looks correct: FILTER_PERIODS_DEFAULT is used as value and FILTER_PERIODS_KEY is used as key.
However your way to define default values for UserDefaults is wrong.
As soon as possible (in awakeFromNib or applicationWillFinishLaunching) add
let defaults = UserDefaults.standard
let defaultValues = [InAppProperties.FILTER_PERIODS_KEY: "M1,M5,M15,M30,H1,H4,D1"]
defaults.register(defaults: defaultValues)
The code has to be executed every time the application launches even if the value has changed meanwhile.

How to wrap function try catch so force unwrap nil doesn't crash the app?

I am getting these errors often when a function I call uses something like:
optionalVar!
"Unexpectedly found nil while unwrapping an Optional"
I am unsure how to deal with functions that fail sometimes if I don't always have control over the inner code?
Is there a way to protect around such crashes? In most languages I could put try catches around most things.
When I do something like:
if let result = Blah.someExternalFunction(html: "some bad html") { }
This can still fail inside "someExternalFunction", even after trying to add try? in front of it.
Thanks
What you're looking for is if let
If you have an optional value, you can simply do something like this to "try" it:
if let val = optionalVar{
//val is already unwrapped
}
else{//it was nil}
Another option is to use a guard statement. It works similarly.
guard let val = optionalVar
else{
//the value is nil, so you need to exit the current function
return
}
//'val' is now unwrapped for any code below the guard-else statement

Catching exceptions when unarchiving using NSKeyedUnarchiver

We've got a Swift class which inherits from NSObject and implements NSCoding. We need to change the name of the class in code and in the archives on disk. Fortunately, we don't need to retain the data. Wiping the cached files and returning default data will be fine. The trouble is detecting when it fails and handling it appropriately.
Ok, to start with the data is written out like this (ignore liberties with force unwrapping etc. it's just to keep the question concise):
let someObject: [Int: MyClass] = ...
let data = NSKeyedArchiver.archivedData(withRootObject: someObject)
try! data.write(to: someUrl, options: .atomic)
This works fine. Our existing method of decoding is this (again, our code is safer in practice):
let someObject = NSKeyedUnarchiver.unarchiveObject(withFile: someUrl.path)! as! [Int: MyClass]
Now, when we rename our class, we are going to have to deal with the cases where it fails to decode. With NSKeyedUnarchiver, you can set a delegate which will have a method called if decoding fails. Before changing it, I wanted to re-write our existing decoder to make sure it can decode as is before I make any changes. Unfortunately, it can't. Here is what we've got:
let fileData = fileManager.contents(atPath: fileUrl.path)!
let unarchiver = NSKeyedUnarchiver(forReadingWith: fileData)
guard let myObject = try! unarchiver.decodeTopLevelObject() as? [Int: MyClass] else {
return [Int: MyClass]()
}
Now, I didn't expect any issues with this. However, the call to decodetopLevelObject() fails every time, simply returning nil (it's definitely not the cast that's the problem). I have no idea why. The documentation for this is effectively non-existent. There is a similar call which is decodeObject(), but that also fails for no obvious reason. I've tried setting the delegate and implementing the method, but it never gets called.
Where are we going wrong?
Try using decodeObject(forKey:) with NSKeyedArchiveRootObjectKey or similar API.

Difference between UserDefaults() and UserDefaults.standard

Is there a difference between UserDefaults() and UserDefaults.standard in Swift 3.0?
UserDefaults - Gives you a new object, each object is allocated a different memory and deallocated when object scope is finished.
UserDefaults.standard - Gives you the singleton object by using the class method standard the object received by this method is allocated single memory throughout the application.
And the usage of them if you´re interesedted in that:
// Set
UserDefaults.standard.set("YOUR STRING", forKey: "key")
UserDefaults().set("YOUR STRING", forKey: "key")
// Get
UserDefaults.standard.string(forKey: "key")
UserDefaults().string(forKey: "key")
let ud = UserDefault()
let uds = UserDefaults.standard
Both objects will use the same underlying plist file to get/set
values.
Setting a value for a key with "ud" gives the same value
using the "uds" object.
You can create and use instances of "UserDefault" created with the convenience "init" method, but the Foundation documentation encourages a developer to use the singleton accessible by the "standard" variable.

Swift NSUserDefaults first time nil

Hi my app crashes the first time I run it. This is my code:
let State = save.stringForKey("StateSave")
let City = save.stringForKey("CitySave")
let Vehicle = save.stringForKey("ModelNumberSave")
let ExtensionPeriod = save.stringForKey("ExtensionPeriodChoosed")
let Location = "Location"
if ExtensionPeriod == nil {
let name = ""
var FieldChoosed: Void = save.setObject(name, forKey: "ExtensionPeriodChoosed")
save.synchronize()
}
save.synchronize()
var DetailNames = ["\(State!)","\(City!)","\(Location)","\(Vehicle!)","\(ExtensionPeriod!)"]
I get a nil on this line:
var DetailNames =
["(State!)","(City!)","(Location)","(Vehicle!)","(ExtensionPeriod!)"]
In fact ExtensionPeriod is nil. But I don't understand why... ExtensionPeriod is nil, but with the code I write, ExtensionPeriod will be like "" so it's not nil. Please help me.
stringForKey returns nil when there has not been a value saved already.
You need to give your values a default. The easiest way to do this is with the ?? operator, that replaces nil with another value:
let State = save.stringForKey("StateSave") ?? "-"
Some general advice: you need to stop using ! so much. Usually when something returns nil, it’s for a good reason – it might be nil. When you unwrap it with !, your program will crash (with not much helpful info as to why). Similarly, it’s usually a bad sign if you’re comparing values to nil.
Instead, take a look at this list of optional handling techniques for some alternatives.
Airspeed Velocity has a good solution for the proper way to accomplish what you want to do, but he did not really explain why what you did does not work, so I will address that aspect of this question.
if ExtensionPeriod == nil {
let name = ""
var FieldChoosed: Void = save.setObject(name, forKey: "ExtensionPeriodChoosed")
save.synchronize()
}
That block of code does not set ExtensionPeriod, thus ExtensionPeriod is still nil. All it does is set the value for the key "ExtensionPeriodChoosed" in the NSUserDefaults to no longer be nil. The local variable ExtensionPeriod, however, still has nil. ExtensionPeriod doesn't just magically point to the variable stored in NSUserDefaults, such that when you update NSUserDefaults, it automatically updates the variable. Instead, it copies the variable at the time that it is created.
Here is some sample code that demonstrates this:
NSUserDefaults.standardUserDefaults().setValue("string", forKey: "s")
NSUserDefaults.standardUserDefaults().synchronize()
var s = NSUserDefaults.standardUserDefaults().valueForKey("s")
NSUserDefaults.standardUserDefaults().setValue("string2", forKey: "s")
NSUserDefaults.standardUserDefaults().synchronize()
var y = NSUserDefaults.standardUserDefaults().valueForKey("s")
println(s)
println(y)
outputs:
"string"
"string2"
For your code to work, if you were to keep it the same structure (although you really shouldn't), you would need to change ExtensionPeriod to a var, and then in your if block, after you synchronize save, you would have to reassign ExtensionPeriod to save.valueForKey("ExtensionPeriodChoosed").
One way to make sure that your app's defaults are set is to use NSUserDefault's registerDefaults(_: [NSObject : AnyObject]) function. In Objective-C, I often put in the + (void)initialize class method, but overriding the init() of the application delegate should work just as well.