Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Swift and NSUserDefaults - EXC_BAD_INSTRUCTION when user defaults empty

Tags:

ios

swift

I'm converting one of my projects to Swift, file by file. I have a strange behavior with NSUserDefaults. I use an NSString and not a String for compatibility with other code

var selectedMonth : NSString {
get {
    return NSUserDefaults.standardUserDefaults().objectForKey(AppUserDefaults.SelectedMonth.value()) as NSString
}
set {
    NSUserDefaults.standardUserDefaults().setObject(self, forKey: AppUserDefaults.SelectedMonth.value())
    NSUserDefaults.standardUserDefaults().synchronize()
}
}

The method works correctly for getting/setting the property, but when the app is executed the first time and the user defaults are empty, I receive an EXC_BAD_INSTRUCTION error on return NSUserDefaults.standardUserDefaults().objectForKey(AppUserDefaults.SelectedMonth.value()) as NSString

The problem is not in the return, since I can "expand" the statement as this:

    let defaultKey = AppUserDefaults.SelectedMonth.value()
    let defaults = NSUserDefaults.standardUserDefaults()  // defaults is correctly set
    let result : AnyObject = defaults.objectForKey(defaultKey)  // <-- the error is here
    return result as NSString

A similar statement in objective C works correctly, returning nil, but I see that the declaration of objectForKey in swift is

func objectForKey(defaultName: String!) -> AnyObject!

Note the exclamation mark at the end, it seems that an object MUST be returned from the function, but I think that the correct declaration (to make it work like in objc) should end with a ? So I think that the behavior changed, and it's not written in the documentation.

Do you think it's a bug (so I'll file it) or am I missing something? Thanks

like image 486
LombaX Avatar asked Jun 04 '14 08:06

LombaX


Video Answer


2 Answers

edit
This answer is no longer updated since in the final Swift release Apple changed the method declaration of objectForKey on NSUserDefaults (and many other methods bridged from objective c) to return an optional ( ? ) and no more an Implicitly Unwrapped Optional ( ! )
end edit

Ok, I read better the docs and the meaning of the ! in a declaration, which is different from a ! when using a variable.

! in a declaration means that that variable is implicitly unwrapped (on the Swift book, read "Implicitly Unwrapped Optionals" and "Unowned References and Implicitly Unwrapped Optional Properties" chapters to understand better what it means

In simple words, it means that the return value can still be nil, but since it happens "rarely", the return value / variable is not marked as optional (with the ?). This is made to avoid the needing for the programmer to unwrap the value every time (checking with an if statement and unwrapping the value) having a cleaner code.

However, the first problem was that the EXC_BAD_INSTRUCTION was given on the wrong line, since with this statement

let defaultKey = AppUserDefaults.SelectedMonth.value()
let defaults = NSUserDefaults.standardUserDefaults() 
let result : AnyObject = defaults.objectForKey(defaultKey) 
return NSString(UTF8String: "")

no error is given.(the result was not returned but the objectForKey call did not fail) So this brought me on the right way again and understanding the meaning of Implicitly Unwrapped Optionals

The solutions are simple, since the returned value can be nil the variable selectedMonth should have a ? (to be marked as optional) or a ! (to be marked as implicitly unwrapped optional)

Examples

1) return AnyObject? optinal value as a type for the var selectedMonth

var selectedMonth : AnyObject? {
get {
    return NSUserDefaults.standardUserDefaults().objectForKey(AppUserDefaults.SelectedMonth.value())

}
set {
    NSUserDefaults.standardUserDefaults().setObject(newValue, forKey: AppUserDefaults.SelectedMonth.value())
    NSUserDefaults.standardUserDefaults().synchronize()
}
}

2) return an optional NSString?, cast the return type to AnyObject? and then to NSString?

var selectedMonth : NSString? {
get {
    return NSUserDefaults.standardUserDefaults().objectForKey(AppUserDefaults.SelectedMonth.value()) as AnyObject? as NSString?
}
set {
    NSUserDefaults.standardUserDefaults().setObject(newValue, forKey: AppUserDefaults.SelectedMonth.value())
    NSUserDefaults.standardUserDefaults().synchronize()
}
}

this is like writing

var selectedMonth : NSString? {
get {
    var returnValue : AnyObject? = NSUserDefaults.standardUserDefaults().objectForKey(AppUserDefaults.SelectedMonth.value())
    return returnValue as NSString?
}
set {
    NSUserDefaults.standardUserDefaults().setObject(newValue, forKey: AppUserDefaults.SelectedMonth.value())
    NSUserDefaults.standardUserDefaults().synchronize()
}
}

3) return an implicitly wrapped NSString!

var selectedMonth : NSString! {
get {
    var returnValue : AnyObject! = NSUserDefaults.standardUserDefaults().objectForKey(AppUserDefaults.SelectedMonth.value())
    return returnValue as NSString!
}
set {
    NSUserDefaults.standardUserDefaults().setObject(newValue, forKey: AppUserDefaults.SelectedMonth.value())
    NSUserDefaults.standardUserDefaults().synchronize()
}
}

4) short version

var selectedMonth : NSString! {
get {
    return NSUserDefaults.standardUserDefaults().objectForKey(AppUserDefaults.SelectedMonth.value()) as AnyObject! as NSString!
}
set {
    NSUserDefaults.standardUserDefaults().setObject(newValue, forKey: AppUserDefaults.SelectedMonth.value())
    NSUserDefaults.standardUserDefaults().synchronize()
}
}
like image 180
LombaX Avatar answered Nov 06 '22 04:11

LombaX


Try this

if let myObject : AnyObject = defaults.objectForKey(defaultKey){
    // grab myObject here
    return myObject;
}else{
    // .. //
    return nil;
}
like image 27
Jirat Ki Avatar answered Nov 06 '22 05:11

Jirat Ki