I have been reading about Optionals in Swift, and I have seen examples where if let
is used to check if an Optional holds a value, and in case it does – do something with the unwrapped value.
However, I have seen that in Swift 2.0 the keyword guard
is used mostly. I wonder whether if let
has been removed from Swift 2.0 or if it still possible to be used.
Should I change my programs that contain if let
to guard
?
if let
and guard let
serve similar, but distinct purposes.
The "else" case of guard
must exit the current scope. Generally that means it must call return
or abort the program. guard
is used to provide early return without requiring nesting of the rest of the function.
if let
nests its scope, and does not require anything special of it. It can return
or not.
In general, if the if-let
block was going to be the rest of the function, or its else
clause would have a return
or abort in it, then you should be using guard
instead. This often means (at least in my experience), when in doubt, guard
is usually the better answer. But there are plenty of situations where if let
still is appropriate.
Guard can improve clarity
When you use guard you have a much higher expectancy for the guard to succeed and it's somewhat important that if it doesn't succeed, then you just want to exit scope early. Like you guard to see if a file/image exists, if an array isEmpty or not.
func icon() -> UIImage {
guard let image = UIImage(named: "Photo") else {
return UIImage(named: "Default")! //This is your fallback
}
return image //-----------------you're always expecting/hoping this to happen
}
If you write the above code with if-let it conveys to the reading developer that it's more of a 50-50. But if you use guard you add clarity to your code and it implies I expect this to work 95% of the time...if it ever failed, I don't know why it would; it's very unlikely...but then just use this default image instead or perhaps just assert with a meaningful message describing what went wrong!
Avoid
guard
s when they create side effects, guards are to be used as a natural flow. Avoid guards whenelse
clauses introduce side effects. Guards establish required conditions for code to execute properly, offering early exitWhen you perform significant computation in the positive branch, refactor from
if
to aguard
statement and returns the fallback value in theelse
clause
Also as a result of the above suggestions and clean code, it's more likely you will want/need to add assertions into failed guard statements, it just improves readability and makes it clear to other developers what you were expecting.
guard let image = UIImage(named: selectedImageName) else { // YESSSSSS assertionFailure("Missing \(selectedImageName) asset") return } guard let image = UIImage(named: selectedImageName) else { // NOOOOOOO return }
From: Erica Sadun's Swift Style book + some modifications
(you won't use asserts/preconditions for if-let
s. It just doesn't seem right)
Using guards also help you improve clarity by avoiding pyramid of doom. See Nitin's answer.
Guard creates a new variable
There is one important difference that I believe no one has explained well.
Both guard
and if let
unwrap the variable however
With guard
you are creating a new variable that will exist outside the else
statement.
With if let
you aren't creating any new variable—after the else statement, you only enter the code block if the optional is non-nil. The newly created variable exists only inside the code block not after!
guard:
func someFunc(blog: String?) {
guard let blogName = blog else {
print("some ErrorMessage")
print(blogName) // will create an error Because blogName isn't defined yet
return
}
print(blogName) // You can access it here ie AFTER the guard statement!!
//And if I decided to do 'another' guard let with the same name ie 'blogName' then I would create an error!
guard let blogName = blog else { // errorLine: Definition Conflicts with previous value.
print(" Some errorMessage")
return
}
print(blogName)
}
if-let:
func someFunc(blog: String?) {
if let blogName1 = blog {
print(blogName1) // You can only access it inside the code block. Outside code block it doesn't exist!
}
if let blogName1 = blog { // No Error at this line! Because blogName only exists inside the code block ie {}
print(blogName1)
}
}
For more info on if let
do see: Why redeclaration of optional binding doesn't create an error
Guard requires scope exiting
(Also mentioned in Rob Napier's answer) :
You MUST have guard
defined inside a func. It's major purpose is to abort/return/exit scope, if a condition isn't met:
var str : String?
guard let blogName1 = str else {
print("some error")
return // Error: Return invalid outside of a func
}
print (blogName1)
For if let
you don't need to have it inside any func:
var str : String?
if let blogName1 = str {
print(blogName1) // You don't get any errors!
}
When to use if-let
and when to use guard
is often a question of style.
Say you have func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int
and an optional array of items (var optionalArray: [SomeType]?
), and you need to return either 0
if the array is nil
(not-set) or the count
if the array has a value (is set).
You could implement it like this using if-let
:
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int
{
if let array = optionalArray {
return array.count
}
return 0
}
or like this using guard
:
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int
{
guard let array = optionalArray else {
return 0
}
return array.count
}
The examples are functionally identical.
Where guard
really shines is when you have a task like validating data, and you want the function to fail early if anything is wrong.
Instead of nesting a bunch of if-let
s as you get closer to finishing validation, the "success path" and the now successfully bound optionals are all in the main scope of the method, because the failure paths have all returned already.
I'll try to explain the usefulness of guard statements with some (unoptimized) code.
You have a UI where you are validating text fields for user registration with first name, last name, email, phone and password.
If any textField is not containing valid text, it should make that field firstResponder.
here is the unoptimized code:
//pyramid of doom
func validateFieldsAndContinueRegistration() {
if let firstNameString = firstName.text where firstNameString.characters.count > 0{
if let lastNameString = lastName.text where lastNameString.characters.count > 0{
if let emailString = email.text where emailString.characters.count > 3 && emailString.containsString("@") && emailString.containsString(".") {
if let passwordString = password.text where passwordString.characters.count > 7{
// all text fields have valid text
let accountModel = AccountModel()
accountModel.firstName = firstNameString
accountModel.lastName = lastNameString
accountModel.email = emailString
accountModel.password = passwordString
APIHandler.sharedInstance.registerUser(accountModel)
} else {
password.becomeFirstResponder()
}
} else {
email.becomeFirstResponder()
}
} else {
lastName.becomeFirstResponder()
}
} else {
firstName.becomeFirstResponder()
}
}
You can see above, that all the strings (firstNameString, lastNameString etc) are accessible only within the scope of the if statement. so it creates this "pyramid of doom" and has many issues with it, including readability and ease of moving things around (if the fields' order is altered, you have to rewrite most of this code)
With the guard statement (in the code below), you can see that these strings are available outside the {}
and are made use of, if all fields are valid.
// guard let no pyramid of doom
func validateFieldsAndContinueRegistration() {
guard let firstNameString = firstName.text where firstNameString.characters.count > 0 else {
firstName.becomeFirstResponder()
return
}
guard let lastNameString = lastName.text where lastNameString.characters.count > 0 else {
lastName.becomeFirstResponder()
return
}
guard let emailString = email.text where
emailString.characters.count > 3 &&
emailString.containsString("@") &&
emailString.containsString(".") else {
email.becomeFirstResponder()
return
}
guard let passwordString = password.text where passwordString.characters.count > 7 else {
password.becomeFirstResponder()
return
}
// all text fields have valid text
let accountModel = AccountModel()
accountModel.firstName = firstNameString
accountModel.lastName = lastNameString
accountModel.email = emailString
accountModel.password = passwordString
APIHandler.sharedInstance.registerUser(accountModel)
}
If the order of the fields changes, just move respective lines of code up or down, and you are good to go.
This is a very simple explanation and a use case. Hope this helps!
Basic Difference
Guard let
- Early exist process from the scope
- Require score existing like return, Throw etc.
- Create a new variable those can be access out the scope.
if let
- Can not access out the scope.
- no need to return statement. But we can write
NOTE: Both are used to unwrapped the Optional variable.
The clearest explanation I saw was in the Github Swift Style Guide:
if
adds a level of depth:
if n.isNumber {
// Use n here
} else {
return
}
guard
doesn't:
guard n.isNumber else {
return
}
// Use n here
A guard statement is used to transfer program control out of a scope if one or more conditions aren’t met.
The value of any condition in a guard statement must be of type Bool or a type bridged to Bool. The condition can also be an optional binding declaration
A guard statement has the following form:
guard condition else {
//Generally return
}
- Also popular as optional binding
- For accessing optional object we use if let
if let roomCount = optionalValue {
print("roomCount available")
} else {
print("roomCount is nil")
}
I learnt this from swift with Bob..
Typical Else-If
func checkDrinkingAge() {
let canDrink = true
if canDrink {
print("You may enter")
// More Code
// More Code
// More Code
} else {
// More Code
// More Code
// More Code
print("Let me take you to the jail")
}
}
Issues with Else-If
- Nested brackets
- Have to read every line to spot the error message
Guard Statement A guard block only runs if the condition is false, and it will exit out of the function through return. If the condition is true, Swift ignores the guard block. It provides an early exit and fewer brackets.+
func checkDrinkProgram() {
let iCanDrink = true
guard iCanDrink else {
// if iCanDrink == false, run this block
print("Let's me take you to the jail")
return
}
print("You may drink")
// You may move on
// Come on.
// You may leave
// You don't need to read this.
// Only one bracket on the bottom: feeling zen.
}
Unwrap Optionals with Else-If
A guard statement is not only useful for replacing a typical conditional block with an else-if statement, but also great for unwrapping optionals by minimizing the number of brackets. To compare, let's first begin how to unwrap multiple optionals with else-if. First, let us create three optionals that will be unwrapped.
var publicName: String? = "Bob Lee"
var publicPhoto: String? = "Bob's Face"
var publicAge: Int? = nil
The Worst Nightmare
func unwrapOneByOne() {
if let name = publicName {
if let photo = publicPhoto {
if let age = publicAge {
print("Bob: \(name), \(photo), \(age)")
} else {
print("age is mising")
}
} else {
print("photo is missing")
}
} else {
print("name is missing")
}
}
The code above certainly works but violates the DRY principle. It's atrocious. Let us break it down.+
Slightly Better The code below is more readable than above.+
func unwrapBetter() {
if let name = publicName {
print("Yes name")
} else {
print("No name")
return
}
if let photo = publicPhoto {
print("Yes photo")
} else {
print("No photo")
return
}
if let age = publicAge {
print("Yes age")
} else {
print("No age")
return
}
}
Unwrap with Guard The else-if statements can be replaced with guard.+
func unwrapOneByOneWithGuard() {
guard let name = publicName else {
print("Name missing")
return
}
guard let photo = publicPhoto else {
print("Photo missing")
return
}
guard let age = publicAge else {
print("Age missing")
return
}
print(name)
print(photo)
print(age)
}
Unwrap Multiple Optionals with Else-If So far, you've been unwrapping optionals one by one. Swift allows us to unwrap multiple optionals at once. If one of them contains nil, it will execute the else block.
func unwrap() {
if let name = publicName, let photo = publicPhoto, let age = publicAge {
print("Your name is \(name). I see your face right here, \(photo), you are \(age)")
} else {
// if any one of those is missing
print("Something is missing")
}
}
Be aware that when you unwrap multiple optionals at once, you can't identify which contains nil
Unwrap Multiple Optionals with Guard Of course, we should use guard over else-if.+
func unwrapWithGuard() {
guard let name = publicName, let photo = publicPhoto, let age = publicAge else {
// if one or two of the variables contain "nil"
print("Something is missing")
return
}
print("Your name is \(name). I see your, \(photo). You are \(age).")
// Animation Logic
// Networking
// More Code, but still zen
}
来源:https://stackoverflow.com/questions/32256834/swift-guard-vs-if-let