How do I avoid using the ! operation doing a force unwrap as using this is usually a bad option.
What is the better option with code like the following where using
Before unwrapping an optional variable you should must check for nil
otherwise your app will crash if variable contain nil
.
And checks can be performed in several ways like:
if let
guard
if-else
And which one to use completely depends on requirement.
You can just replace this code
if middleName == nil {
return "\(firstName) \(lastName)"
}else{
return "\(firstName) \(middleName!) \(lastName)"
}
by
return "\(firstName)\(middleName != nil ? " \(middleName!) " : " " )\(lastName)"
OR
you can also use Nil coalescing operator (a ?? b) unwraps an optional a if it contains a value, or returns a default value b if a is nil.
Your instructor is, broadly speaking, correct. Definitely in this case. There's no reason for making this so special case and forcing duplicated code.
func fullName() -> String {
return [firstName, middleName, lastName] // The components
.flatMap{$0} // Remove any that are nil
.joined(separator: " ") // Join them up
}
This just joins all the non-nil parts of the name with spaces. The other answers here are also fine, but they don't scale as well to adding more optional pieces (like a "Mr." title or "Jr." suffix).
(This is in Swift3 syntax. Swift 2 is very similar, it's joinWithSeparator(_:)
instead.)
Use the if let
or guard
constructs:
func fullName() -> String {
if let middleName = middleName {
return "\(firstName) \(middleName) \(lastName)"
} else {
return "\(firstName) \(lastName)"
}
}
func fullName() -> String {
guard let middleName = middleName else {
return "\(firstName) \(lastName)"
}
return "\(firstName) \(middleName) \(lastName)"
}
I've put the guard
statement in for completeness but as others have commented this is more commonly used in an error/failure case.
I would also advise against using string interpolation for Strings. They are already strings, there is no need to use the description
of each name in a new string.
Consider return firstName + " " + lastName
. See Difference between String interpolation and String initializer in Swift for cases when string interpolation could return an unexpected result.
What you did will work and indeed, once you know it's not nil, using ! is a correct way to force unwrap it.
However, Swift has feature called Optional Binding (https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/TheBasics.html).
In your case it would be like this:
func fullName() -> String {
if let middle = middleName {
return "\(firstName) \(middleName) \(lastName)"
} else {
return "\(firstName) \(lastName)"
}
}
What the optional binding does is, as Apple says in the above link, "You use optional binding to find out whether an optional contains a value, and if so, to make that value available as a temporary constant or variable". So inside your brackets you have access to middle
and can use it as a known non-nil.
You can also chain these so that you have access to multiple temporary constants and don't need to chain if statements. And you can even use a where
clause to evaluate an optional condition. Again from Apple's docs above:
if let firstNumber = Int("4"), secondNumber = Int("42") where firstNumber < secondNumber {
print("\(firstNumber) < \(secondNumber)")
}
In this case, your instructor is wrong. Your function is absolutely safe. middleName will not change from not nil to nil behind your back. Your function may crash if you make some spelling error, and type the name of a different variable instead of middleName, but that would be a bug anyway and the crash would lead you to the bug.
But usually "if let ... " is the better way to handle this, because it combines the test and the unwrapping.
There are also situations where you don't say "if it's nil, it will crash, that's bad" but "if it's nil then I want it to crash" (usually because you know it can only be nil if there is a bug somewhere in your code). In that case ! does exactly what you want.