Unwrapping NSMutableArray from NSUserDefaults - ios

I am trying to unwrap a NSMutableArray from user defaults, but keep getting the error unexpectedly found nil while unwrapping an Optional value. I tried to check for nil after getting the array, but it is happening on the line that is getting the array from the UserDefaults, so I don't know how to fix this error. Can anyone help?
var classURLs: NSMutableArray = NSMutableArray();
let defaults: NSUserDefaults = NSUserDefaults(suiteName: "group.myCompany.Array")!;
classURLs = NSMutableArray(object: defaults.objectForKey("Class URLs")!);

NSUserDefaults.objectForKey returns an optional for a reason – if the key isn’t present, you get nil back. You shouldn’t just force-unwrap it with !
The most common case is this is the first time you’ve tried reading it without having ever written it.
In which case you probably want a default value, perhaps an empty array:
let classURLs = defaults.stringArrayForKey("Class URLs") as? [String] ?? []
(?? substitutes the value on the right if the value on the left is nil)
Note, it’s probably better if you’re writing Swift to go with a Swift array (e.g. [String]) rather than NSMutableArray, unless all you are going to do is pass it straight into a call to Objective-C.
You can also avoid the ! you’re using with the NSUserDefaults init by using optional chaining:
let defaults = NSUserDefaults(suiteName: "group.myCompany.Array")
var classURLs = defaults?.stringArrayForKey("Class URLs") as? [String] ?? []

If the key does not exist, the forced unwrap (!) of the nil object will result in a crash. You need to account for this case.
var classURLs = NSMutableArray(object:
NSUserDefaults.standardUserDefaults().objectForKey("foo") ?? [])
NB: transitioning from Objective-C? You don't need the semicolons! ;-)

Related

App crashed when getting string from Dictionary (swift)

I converted a JSON to Dictionary and got some String by
title = json?.objectForKey("Titel_Live") as! String
But some times app will be crashed. I cannot reproduce this problem, just get information from crash reports.
Could someone help me and tell why? Thanks
Error at line 163
Crash reports
title = json?.objectForKey(“Titel_live”) as! String
This line of code where you are doing force unwrapped (Don't force the cast using !) is the cause means if object with key Titel_live dot not find then should be crashed, better go with optional chaining or use gaurd but yes your Json does't contain any object with key Titel_live(may be spelling mistake or object is array so validate once).
//better go like this check if exist or not.
if let t = json?.objectForKey(“Titel_live”) {
title = t
}
You should not force the casting to String.
You can try :-
title = json?.objectForKey("Title_Live") as? String (if title is optional variable)
if title is not optional then use:
title = (json?.objectForKey("Title_Live") as? String ?? "")
Because objectForKey will return nil if no value is associated with that key and force casting nil to String fails and causes crash.

NSUserDefaults optionals in Swift

I'm trying to deal with compiler in case of optional values. Task is very simple, my func fetches user defaults to appear them in tableview. If user launches app for the first time, it setts default values. Setting default values works fine (checked with print log), but fetching causes:
fatal error: unexpectedly found nil while unwrapping an Optional value
I'm already worked with optionals for a long time, but, perhaps I'm still confused about them, 'cos I see that everything seems to be correct and even compiler says, that everything is ok.
func getFiltersSetts() -> [String] {
let userDefs = NSUserDefaults.standardUserDefaults()
var defsArray = [String]()
if (userDefs.objectForKey("gender") != nil) {
defsArray.append((userDefs.objectForKey("gender")?.stringValue)!)
defsArray.append((userDefs.objectForKey("age")?.stringValue)!)
defsArray.append((userDefs.objectForKey("online")?.stringValue)!)
}
else {
userDefs.setObject("Male", forKey: "gender")
userDefs.setObject("21-30", forKey: "age")
userDefs.setObject("Online", forKey: "online")
}
return defsArray
}
You are force unwrapping your optionals, and you should get them as strings before appending them to your array.
A cleaner way to set the defaults would be to coalesce the unwrapping of your optionals, Try the following approach:
func getFiltersSetts() -> [String] {
let userDefs = NSUserDefaults.standardUserDefaults()
var defsArray = [String]()
defsArray.append(userDefs.stringForKey("gender") ?? "Male")
defsArray.append(userDefs.stringForKey("age") ?? "21-30")
defsArray.append(userDefs.stringForKey("online") ?? "Online")
return defsArray
}
The code above uses the coalesce (??) operator. If your optional, say userDefs.stringfForKey("gender"), returns nil, the coalesce operator will use the default value "Male".
Then at a later time you can save your user defaults (or create them) if they haven't been set before.
Also, is worth noticing that you should be unwrapping your optionals using the if let notation. Instead of comparing if its != nil, as this will prevent you from force unwrapping them inside the code block.
I hope this helps!
Apple highly recommends to set default values via registerDefaults of NSUserDefaults.
As soon as possible (at least before the first use) set the default values for example in applicationDidFinishLaunching:
let userDefs = NSUserDefaults.standardUserDefaults()
let defaultValues = ["gender" : "Male", "age" : "21-30", "online" : "Online"]
userDefs.registerDefaults(defaultValues)
The default values are considered until a new value is set.
The benefit is you have never to deal with optionals nor with type casting.
func getFiltersSetts() -> [String] {
let userDefs = NSUserDefaults.standardUserDefaults()
return [userDefs.stringForKey("gender")!,
userDefs.stringForKey("age")!,
userDefs.stringForKey("online")!]
}
The forced unwrapping is 100% safe because none of the keys can ever be nil.
Please read Registering Your App’s Default Preferences in Preferences and Settings Programming Guide
You can create a default key "firstBootCompleted". Then use:
let defaults = NSUserDefaults.standardUserDefaults()
if (defaults.boolForKey("firstBootCompleted") == false) {
defaults.setObject(true, forKey: "firstBootCompleted")
// Initialize everything for your first boot below
} else {
// Initialize everything for not your first boot below
}
The reason this works is that boolForKey returns false when the object for key is nil
You are storing all the value as String and then at the time of fetching
you are typecast the value again in String
userDefs.objectForKey("gender")?.stringValue
A String can not be convert as String again.
try to access as
userDefs.stringForKey("gender")

Intricate access to dictionary key

From a server I receive a JSON string, then I try to convert it to an NSDictionary this way:
let JSON = try NSJSONSerialization.JSONObjectWithData(rToData!, options:[])
guard let JSONDictionary:NSDictionary = (JSON as! NSDictionary) else {
print("My grandma is way more NSDictionary than this")
return
}
Once converted, I try to get some data contained in the dictionary: in particular I need an array I can access this way:
let myArray = JSONDictionary["data1"][0]["data2"];
XCode really doesn't like this idea, it puts an arrow under the first bracket and says Value of optional type "AnyObject?" not unwrapped, did you mean to use "!" or "?" ?. I follow its suggestion and I insert a "!", converting my preceding code to this:
let myArray = JSONDictionary["data1"]![0]["data2"];
At this point, the following line (where I count the number of elements in data2) shows an error, stating AnyObject has no member count.
The only thing that seems to work fine is this solution but, apart from being ugly and unreadable, I really don't understand it:
let myArray = (JSONDictionary["data1"]?[0]["data2"])!;
Can you help me understand why this basic access to a key in a dictionary must be so intricate?
I must say I like Swift but I spend a lot of time dealing with optionals and bizarre XCode alerts.
There is no guarantee that your JSON dictionary will contain a value for the key data1 (OK, you know it will, but Swift doesn't) so JSONDictionary["data1"] returns an optional. You need to unwrap the optional with ? or !
Also, since you have an NSDictionary, not a Swift dictionary, Swift doesn't know the type of the values, so they are AnyObject. Now again, you know it is an array, but Swift doesn't so you get an error stating that AnyObject doesn't have a count method.
While it is more verbose, it is cleaer for both the compiler and anyone else looking at your code if you split the line into multiple lines. It also lets you downcast the various objects so that Swift knows what is going on and handle any malformed JSON;
if let array1 = JSONDictionary["data1"] as? NSArray {
if let dictionary1 = array1[0] as? NSDictionary {
if let data2Array = dictionary1["data2"] as? NSArray {
let count=data2Array.count
}
}
}
You could implement appropriate else statements to handle errors
Optionals are one of Swift's most powerful features. They help avoid a whole family of bugs associated with uninitialised variables and special sentinnel values for boundary conditions. It is important that you learn how they can help you and not just throw ? or ! at your code until it compiles.

NSUserDefaults properly storing Strings in SpriteKit Swift

So I set up a NSUserDefault to store a string in my GameViewController
NSUserDefaults.standardUserDefaults().setObject("_1", forKey: "SkinSuffix")
The idea is it stores a suffix which I will attach to the end of an image name in order to save what skin of a character the player should use.
When I call the value in my GameScene class like so
var SkinSuffix = NSUserDefaults.standardUserDefaults().stringForKey("SkinSuffix")
println(SkinSuffix)
it prints "Optional("_1")" instead of just "_1" so when I try to change the name of my image file like so, it doesn't load the image file
hero = SKSpriteNode(texture: heroAtlas.textureNamed("10Xmini_wizard\(SkinSuffix)"))
How do I fix this issue?
You can unwrap the String using the Optional Binding construct. This avoids a crash of the app if the value is nil.
if let skinSuffix = NSUserDefaults.standardUserDefaults().stringForKey("SkinSuffix") {
println(skinSuffix)
}
Update: As correctly suggested in the comment below, I am putting the retrieved value in a constant (let). We should always use constants when we don't need to change the value. This way the Swift compiler can make some optimizations and does prevent us from changing that value.
That's because it's implicitly an optional not of type String. You need to case it as such or unwrap the optional in your println statement.
var SkinSuffix = NSUserDefaults.standardUserDefaults().stringForKey("SkinSuffix") as! String
Or in your println: println(SkinSuffix!)
As a side note, you should you camelCase for your variable names.
You can use "??" Nil Coalescing Operator to help you dealing with nil and it also
allows you to specify a default string value.
NSUserDefaults().setObject("_1", forKey: "SkinSuffix")
let skinSuffix = NSUserDefaults().stringForKey("SkinSuffix") ?? ""
println(skinSuffix) // "_1"

fatal error: unexpectedly found nil while unwrapping an Optional value(When adding to a array)

I have some code that receives value from a segue and replaces a certain element of an array with the index number that I have.
The initialization of the variables is:
var noteTitles: [String] = ["Sample Note"]
var noteBodies: [String] = ["This is what lies within"]
var selectedNoteIndex: Int!
var newTitle: String!
var newBody: String!
and I have a segue that makes the last 3 values the values that I want them to be.
under viewDidLoad(), I have this:
if newTitle == nil && newBody == nil {
}
else {
println("\(newTitle)")
println("\(newBody)")
println("\(selectedNoteIndex)")
let realTitle: String = newTitle
let realBody: String = newBody
let realIndex: Int = selectedNoteIndex
noteTitles[realIndex] = realTitle
noteBodies[realIndex] = realBody
}
My logs show this:
New Note Title
This is what lies within
nil
fatal error: unexpectedly found nil while unwrapping an Optional value
and I get
Thread 1: EXC_BAD_INSTRUCTION(code=EXC_i385_INVOP,subcode=0x0)
on the line
let realIndex: Int = selectedNoteIndex
Can anyone tell me what I'm doing wrong?
var varName: Type! declares an implicitly unwrapped optional.
It means that it will be automatically unwrapped when accessing the value with varName, i.e. without using varName!.
Thus, accessing the implicitly unwrapped optional selectedNoteIndex with let realIndex: Int = selectedNoteIndex when its value is actually nil results in the error you got.
Apple's Swift Guide states that:
Implicitly unwrapped optionals should not be used when there is a
possibility of a variable becoming nil at a later point. Always use a
normal optional type if you need to check for a nil value during the
lifetime of a variable.
The reason I was getting these errors is because while segueing back to the main view, I was not using the proper unwind segue, and instead using another show segue, which erased all data that had previously been within the view controller. By creating a unwind segue, I was able to keep the values before the segue to the detail view and prevent the error.
Because you did not assign value for selectedNoteIndex so it shows nil. First, you have to check whether its not nil value.
if let selectedNoteIndex = realIndex{
let realIndex: Int = selectedNoteIndex
}

Resources