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
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()
}
}
Try this
if let myObject : AnyObject = defaults.objectForKey(defaultKey){
// grab myObject here
return myObject;
}else{
// .. //
return nil;
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With