Swift: guard
対 if
と、 guard let
対 if let
(日本語)
It would be more appropriate to see this question as guard
vs if
and guard let
vs if let
.
guard
vs if
Standalone guard
and if
are both simple flow control statements and do not do any unwrapping.
guard
just exits early if its condition isn't met.
Comparison table
guard |
if |
|
---|---|---|
Function | Early exit | Flow control |
Focus on ... | ... the "early exit path": checking conditions are correct before going further. | ... the “happy path”: the behavior of our function when everything has gone according to plan. |
Readability, Clarity | Improved | Can lead to pyramids of doom |
Nested Scope (adds levels of depth) | ❌ | ◯ |
Enclosing Scope | Necessary | Not necessary |
Must ... | return from the scope if the check fails | |
Return / Throw | Necessary | Not necessary |
Think of it as ... | ... validation. | ... simple flow control |
Preferred location | At the start of the scope |
Examples
let array = ["a", "b", "c"]
func at(at index: Int) -> String?{
// exit early when index is out of bound
guard index >= 0, index < array.count else { return nil }
return array[index]
}
print(at(1))
let array = ["a", "b", "c"]
let index = 1
if index >= 0, index < array.count {
print(array[index])
} else {
print(nil)
}
guard let
vs if let
guard let
and if let
both unwrap optionals.
guard let
, like guard
, exits early if its condition isn't met.
Comparison table
guard let |
if let |
|
---|---|---|
Function | Unwraps optionals | Unwraps optionals |
Designed to ... | ... exit the current scope if the check fails. | ... just unwrap some optionals. |
Focus on ... | ... the "early exit path": checking conditions are correct before going further. | ... the “happy path”: the behavior of our function when everything has gone according to plan. |
Enclosing Scope | Necessary | Not necessary |
Must ... | return from the scope if the check fails | |
Return / Throw | Necessary | Not necessary |
Readability, Clarity | Improved | Can lead to pyramids of doom |
Nested Scope (adds levels of depth) | ❌ | ◯ |
Visibility | The unwrapped optional is visible in the scope after the guard let statement. |
The unwrapped optional is only visible in the if let statement's code block scope |
Think of it as ... | ... validating then unwrapping some optionals. | ... just unwrapping some optionals. |
Preferred location | At the start of the scope | |
Unwrap multiple optionals at once | ◯ | ◯ |
Side Effects | Should be avoided | Ok |
Examples
guard let
All the strings (firstNameString, lastNameString, emailString, passwordString) are accessible in the scope after the guard statements, if all the fields are valid:
func validateThenRegisterUser() {
// validation
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
}
// The validated information can now be used for registration
let newUser = User()
newUser.firstName = firstNameString
newUser.lastName = lastNameString
newUser.email = emailString
newUser.password = passwordString
APIHandler.sharedInstance.registerUser(newUser)
}
if let
All the strings (firstNameString, lastNameString, emailString, passwordString) are accessible only within the scope of the if
statement code block.
This creates a "pyramid of doom" which presents many issues like:
- readability,
- ease of reordering/refactoring the code. For example, if the fields' validation order was altered, you would have to rewrite most of the code.
func validateThenRegisterUser() {
// validation: starts the pyramid of doom
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{
// The validated information can now be used for registration
let newUser = User()
newUser.firstName = firstNameString
newUser.lastName = lastNameString
newUser.email = emailString
newUser.password = passwordString
APIHandler.sharedInstance.registerUser(newUser)
} else {
password.becomeFirstResponder()
}
} else {
email.becomeFirstResponder()
}
} else {
lastName.becomeFirstResponder()
}
} else {
firstName.becomeFirstResponder()
}
}