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.
Related
I have a Meeting class where I parse the user's calendar to determine the properties of that class. Most of these fields are Strings that can potentially have nothing in them. When I discover these fields are empty is it better to set them to empty "" or make them optional and set them to nil?
/**
Parses calendar entry to create a Meeting object.
- parameter calendarEvent: The calendar entry we want to parse
- returns: A Meeting Object with the relevant information extracted from the calendar entry.
*/
public static func parse(calendarEvent: EKEvent) -> Meeting {
let location = calendarEvent.location ?? ""
let description = calendarEvent.notes ?? ""
let allInput = "\(calendarEvent.title)\n\(location)\n\(description)"
let parsedHostCodes = parseHostCode(from: allInput)
let parsedPasscodes = parseParticipantCode(from: allInput, hostCodes: parsedHostCodes)
let parsedPhoneNumbers = parsePhoneNumber(from: allInput, codes: parsedPasscodes + parsedHostCodes)
return Meeting(
UUID: calendarEvent.eventIdentifier,
title: calendarEvent.title,
description: description,
location: location,
startTime: calendarEvent.startDate,
endTime: calendarEvent.endDate,
allday: calendarEvent.isAllDay,
participantCodes: parsedPasscodes,
hostcodes: parsedHostCodes,
phoneNumbers: parsedPhoneNumbers,
host: retrieveHost(from: calendarEvent.organizer),
attendees: parseParticipants(from: calendarEvent.attendees),
provider: allInput.contains(pattern: attRegex) ? .att : .unknown)
}
Optionals are designed to determine the absence of data. If that's what your program checks for, then you really should use the feature. It's well worth it in the long run in situations where you can take advantage of "Optional Chaining" and "Auto-unwrap":
struct Event {
let location: String?
}
struct Day {
let events: [Event]?
}
var events: [Event]?
let currentDay: Day?
events = [Event(location: "conf room A1")]
currentDay = Day(events: events)
if let events = currentDay?.events {
for event in events {
if let location = event.location {
print(location)
}
}
}
There's a lot more going on here than what you see. First of all, I made everything Optional since it's likely the case in your code.
The first line of the "if let" is using "Optional Chaining". If either currentDay or currentDay.events is nil, the "if" block won't get executed. If currentDay and currentDay.events is not nil, currentDay.events will be auto-unwrapped and get assigned to "events" and then code execution will drop into the "if" block. Next, depending if there are any events for that day (i.e. array count), the "if let location = event.location" also checks if location is nil or not, and if it's not nil, will auto-unwrap and assign to "location" and will print the value.
If any of those Optionals are nil, then nothing in your code needs to change. Here I removed the assignment of "events" and "currentDay" so that they're both nil:
var events: [Event]?
let currentDay: Day? = nil
if let events = currentDay?.events {
for event in events {
if let location = event.location {
print(location)
}
}
}
As you can see, taking advantage of Optionals will result and less maintenance and generally cleaner code in the long run.
In general, if you need to check if a variable has a valid value at some point in your code, it is better to set up the variable as an Optional rather than set it up as an initial value. This is exactly what Optionals were designed for, so that you can check at any point in your code whether that specific variable has been set up or not without giving it any meaningful value.
For strings, "" could easily work as well, since in most cases an empty String literal("") is not a meaningful value, so you can easily check for it, but it is better practice to use Optionals, since they provide more sophisticated methods to check if they have been set or not then to check if a String equals "" or not.
This question must have been asked a lot of times.
Well, nil or "" is depend on your property data, nil used when an object or value is may exist or not. and also assigning nil is very handy when you check it's value using guard and if let. nil raised the question of possibility.
You can also use "". it's work also well, but sometimes it's become headache while comparing default empty value from response.
I am new to swift, so if this is question sounds stupid, please forgive me. Below is the example of optional binding. How it works, I understood. But why it is needed, I am unable to understand.
var optionalName: String? = "John Appleseed"
var greeting = "Hello!"
if let name = optionalName {
greeting = "Hello, \(name)"
}
Instead of using the new construct of "If let" we can use the below code. What is the harm in it? why we are taking an extra temporary variable altogether? Thanks in advance.
var optionalName: String = "John Appleseed"
var greeting = "Hello!"
if optionalName != nil {
greeting = "Hello, \(optionalName)"
}
The if let construct allows you to "unwrap" the optional so that the value can be used (when it is not nil).
Simply testing for not nil does not change the fact that you have an optional.
So the if let construct allows you to test and unwrap the optional in one step. It is a convenience and provides a safe way for you to work with an optional.
There is no harm in just testing for not nil, as long as you do not expect to use the wrapped value in any meaningful way (i.e. you are just testing for not nil). Or you plan on force unwrapping with the ! operator every time you want to use the value within the same scope.
In most cases you will probably want to use the value if its not nil and again the if let construct provides a safe and convenient way of doing so.
I have a bit of code to get a string out of userDefaults:
if let userString = (userDefaults.objectForKey("user")) {
userTextField.stringValue = userString as! String
}
First, I have to see if the optional is not nil. Then I have to cast it as a string from AnyObject.
Is there a better way of doing this? maybe a one liner?
Note that your forced cast as! String will crash if a default value for the key "user" exists, but
is not a string. Generally, you can combine optional binding (if let) with an optional cast (as?):
if let userString = userDefaults.objectForKey("user") as? String {
// ... default value for key exists and is a string ...
userTextField.stringValue = userString
}
But actually NSUserDefaults has a dedicated method for that purpose:
if let userString = userDefaults.stringForKey("user") {
// ... default value for key exists and is a string ...
userTextField.stringValue = userString
}
If you want to assign a default string in the case that
the default does not exist, or is not a string, then use the
nil-coalescing operator ??, as demonstrated in
Swift issue with nil found while unwrapping an Optional value NSDefautlts, e.g.:
userTextField.stringValue = userDefaults.stringForKey("user") ?? "(Unknown)"
For the special case NSUserDefaults the best – and recommended – way is to use always non-optional values.
First register the key / value pair in AppDelegate as soon as possible but at least before using it.
let defaults = NSUserDefaults.standardUserDefaults()
let defaultValues = ["user" : ""]
defaults.registerDefaults(defaultValues)
The benefit is you have a reliable default value of an empty string until a new value is saved the first time. In most String cases an empty string can be treated as no value and can be easily checked with the .isEmpty property
Now write just
userTextField.stringValue = userDefaults.stringForKey("user")!
Without arbitrary manipulation of the defaults property list file the value is guaranteed to be never nil and can be safely unwrapped, and when using stringForKey there is no need for type casting.
Another way that i like much to clean this up is to do each of your checks
first, and exit if any aren’t met. This allows easy understanding of what
conditions will make this function exit.
Swift has a very interesting guard statements which can also be used to avoid force unwrap crashes like :
guard let userString = userDefaults.objectForKey("user") as? String else {
// userString var will accessible outside the guard scope
return
}
userTextField.stringValue = userString
Using guards you are checking for bad cases early, making your
function more readable and easier to maintain. If the condition is not
met, guard‘s else statement is run, which breaks out of the function.
If the condition passes, the optional variable here is automatically
unwrapped for you within the scope that the guard statement was
called.
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)
I need to retrieve a setting (with Swift):
var highScoreNumber: NSInteger = 0
var returnValue: [NSInteger]? = NSUserDefaults.standardUserDefaults().objectForKey("food") as? [NSInteger]
if (returnValue != nil) {
highScoreNumber = returnValue as NSInteger
}
I tried this and other variations of the code and I always get
'NSInteger?' not convertible to 'NSInteger'
This is my first foray into Swift, so
Am I missing something simple in the conversion?
Is there a better way to retrieve an NSInteger from settings?
When converting a nullable value to a non-nullable one, there are many good options to choose from.
But in your case, you have two issues. One is the array you’re fetching from NSUserDefaults is an array of integers, so you need to decide which one you want from that array.
If it’s the first one you want, you can use the first property. You can use optional chaining to get it. And since it’s a high score, you probably want to default to zero if it’s not present.
Here’s that in one line:
let returnValue = NSUserDefaults.standardUserDefaults().objectForKey("food") as? [Int]
let highscore = returnValue?.first ?? 0
Things to note about the above: there’s no need to give the type to the left of the = if the type is unambiguously determined by what lies to the right of the =. And it’s better to prefer Int over NSInteger.
The returnValue?.first says “if returnValue is nil, then nil, else the value of first (which, itself, returns nil if the array is empty).” The ?? says “if the left of ?? is nil, then the value on the right of nil, else the unwrapped value from the left”.
That said – do you really mean to store an array? Or do you really just want to store a single integer in the defaults and then get that out directly?
let highscore = NSUserDefaults.standardUserDefaults().integerForKey("food")
// and elsewhere in your code, when saving
NSUserDefaults.standardUserDefaults().setInteger(highscore, forKey: "food")
(integerForKey already returns a 0 default when not present rather than an optional, so no need for any unwrapping)