guard let foo = foo VS guard foo != nil in Swift - ios

I don't understand the concept of making a useless constant if the task is only to unwrap the value:
guard let foo = foo else { return }
vs
guard foo != nil else { return }
What is the difference between these statements? And what is the reason to not use the latter?
Here is some example of using guard let:
var array: [String] = ["pineapple", "potato", "corn"]
guard let lastElement = array.last, lastElement == "corn" else { return false }
And not using let:
guard array.last == "corn" else { return false }
Can't I just go with the second approach as more clean, simple and probably more memory efficient?

With your first example, foo becomes a non-Optional. So, you can do this:
guard let foo = foo else { return }
foo.doMethod()
Whereas without the optional binding, you'd still have an Optional:
guard foo != nil else { return }
foo?.doMethod()
There are plenty of times when having a non-Optional value is easier to deal with, including being able to avoid force unwrapping with !, so there's lots of practicality to the first code sample.
In terms of the second version, you could use it where you want to do a nil check but may not actually use the value in the scope in which you're checking it.
In your example with the array of type [String], yes, you can do your second comparison without the optional binding to check to see if there's a last element:
guard array.last == "corn" else { return false }
You are correct that it is cleaner this way. It is extremely unlikely that it would be more "memory efficient" as you speculated, though, as the compiler would likely optimize away the temporary optional binding.

Related

Value of optional type 'String?' must be unwrapped to a value of type 'String' to an array?

Total new beginner here in Swift, I'm trying to understand typecasting here
I have the below code
#IBAction func randomImage(_ sender: Any) {
let path = Bundle.main.path(forResource: "imageList", ofType: "plist")
let dict = NSDictionary(contentsOfFile: path!)
let data = dict!.object(forKey: "Images") as! [String]
imageView.image = UIImage(named: data.randomElement())
}
As shown above, first I have dict! to ensure that dict is available, then data will be typecasted into [String] which is an array of string.
Now the part that I dont understand is why data.randomElement() giving me error
Value of optional type 'String?' must be unwrapped to a value of type 'String'
Coalesce using '??' to provide a default when the optional value contains 'nil'
Force-unwrap using '!' to abort execution if the optional value contains 'nil'
sure enough based on the suggestion, i can get away with data.randomElement()!, but why is that needed?
The randomElement() function returns an Optional because the collection you are fetching from might be empty. if it is, the function returns nil.
Get out of the habit of using the ! force-unwrap operator (and variants like implicitly unwrapped optionals.) I call ! the "crash-if-nil" operator. That's what it does.
You should rewrite your code using if let syntax (optional binding)
#IBAction func randomImage(_ sender: Any) {
if let path = Bundle.main.path(forResource: "imageList", ofType: "plist"),
let dict = NSDictionary(contentsOfFile: path),
let data = dict.object(forKey: "Images") as? [String],
let image = UIImage(named: data.randomElement()) {
imageView.image = image
} else {
// Unable to load image
}
}
With a compound if let like that the expression quits on each step if the result is nil. If the result is valid, it keeps going on the if statement and the new temporary variable is now a non-optional.
Once you make it through the last let in the compound if let, you have a UIImage in image that you can install into your image view.
If any of the steps fail, the else clause runs. you can do whatever you want there - install a default image, install nil into the imageView to remove it, print to the console, whatever. (Although you could simplify the code a little if you were going to install a nil image into the image view.)
Edit:
In addition to if let optional binding, you can also use guard statements.
if let optional binding says "try to unwrap this optional/optionals. If it succeeds, create a variable that's only defined inside the body of the if statement, and execute the if statement.
In contrast, guard says "try to unwrap this optional/optionals. If it succeeds, continue. If it fails, execute a block of code that exits the current scope.
Guard statements are useful when you need to check a whole series o things and want to keep going when everything is good, but bail out when if something goes wrong.
if let optional binding can lead to ever-increasing levels of indentation:
func foo() {
if let a = anOpitonal {
// Do stuff
if let b = a.someOtherOptional {
// Do more stuff
if let c = b.yetAnotherOptional {
// Still more code that only runs if all 3 unwraps work
}
}
}
}
In contrast, you could write that with guard like this:
func foo() {
guard let a = anOpitonal else { return }
// Do stuff
guard let b = a.someOtherOptional else { return }
// Do more stuff
guard let c = b.yetAnotherOptional else { return }
// Still more code that only runs if all 3 guard statements work
}

In Swift, how do I do 'if let' instead of this?

I'd like to use 'if let' here but I can't get it to work. Can someone help me?
guard animal?.img != nil else {
print ("image is nil")
return
}
let animalImage = UIImage(data: animal!.img!) as UIImage?
saveImageView.image = animalImage
I think you mean "guard let". BTW, you almost always want to avoid force unwrapping.
Also since UIImageView.image is optional you don't need to check the returned value from UIImage constructor
guard let data = animal?.img else {
print ("image data is nil")
return
}
saveImageView.image = UIImage(data: data)
There are several things you can do with an optional value:
var optionalText:String?
...
var text4:String? = optionalText // assign it to another optional variable
var text5 = optionalText // the same, the compiler will infer that text5 is also an optional string
optionalText?.append(" hello") // will only call `append(_:)` on `text` if it's not nil, otherwise this will be ignored
let text6:String = optionalText ?? "It was nil!" // use the coalescing operator
let text7:String = optionalText! // force-unwrap it into a String; if it's nil, your app will crash
// you CANNOT, however, treat it as a non-optional:
let newText = "Hello " + optionalText // this won't compile
// you can unwrap it:
if let text8 = optionalText {
// this code block will ONLY execute if textField.text was not nil
let newString = "Hello "+ text8 // this constant will only work in this block
// in that case, text8 will be of type String (non-optional)
// so it shouldn't be treated as an optional
}
let newString = "Hello "+ text8 // this won't compile
// unwrap it with `guard`:
guard let text8 = optionalText else {
// this code block will ONLY execute if textField.text WAS nil
// and it must return
return
}
let newString = "Hello "+ text8 // this is okay, text8 lives on in case of `guard`
These are the differences between guard let and if let:
if let nonOptional = optional {} will assign a value to a non-optional constant by unwrapping an optional value and execute the code block, but only if the optional isn't nil. The non-optional constant will only live inside the if let { } block. Use this if you want to handle both cases (nil or otherwise) and then move on with your code.
guard let nonOptional = optional else { } will do the same assignment if possible, but code flow will be different afterwards: it will execute the else block in case the optional value is nil, and that else block will have to quit the scope (return, continue, or break); it must not fall through, i.e. execution must not continue right after the block (the compiler will make sure of that). However, your nonOptional constant will live on after this statement. Use this if your code largely depends on the optional value not being a nil: quit early if the condition fails, and otherwise hold on to the non-optional value and use if for the rest of your enclosing scope.
Btw., you can also use var instead of let if it makes sense, in both cases.
The main purpose of guard is to avoid the "pyramid of doom" type of nested checks:
// pyramid of doom
func doomed() {
if condition1 {
if condition2 {
if condition3 {
// success scenario
print("all good")
// ...
} else {
print("failed condition 3")
return
}
} else {
print("failed condition 2")
return
}
} else {
print("failed condition 1")
return
}
}
// avoiding pyramid of doom
func nonDoomed() {
guard condition1 else {
print("failed condition 1")
return
}
guard condition2 else {
print("failed condition 2")
return
}
guard condition3 else {
print("failed condition 3")
return
}
// success scenario
print("all good")
// ...
}
Without guard, your success scenario is hidden in the middle of nested if statements related to error conditions, making your code difficult to read or edit.
With guard, each error condition is handled separately, and after you get them out of the way, you can go on with the success scenario. With guard let, you can also ensure that all the necessary constants and variables are available for the rest of the scope.
What you seem to need is one of two things:
Optionally unwrap and use your optional:
if let realImage = animal?.img {
let animalImage = UIImage(data: animal!.img!) as UIImage?
saveImageView.image = animalImage
}
// otherwise, saveImageView.image will not be assigned a new value
Simply pass an optional value
saveImageView.image = animal?.img
This should work because the left-hand side and the right-hand side are both UIImage? optionals.
This should do it, you assign your image data to first variable, then use it to assign to the second one:
if let img = animal?.img, let animalImage = UIImage(data: img) {
//do something
}
Probably don't need as? Data and as? UIImage, it depends on your model.
You can do it like below...
if let imgName = animal?.img {
saveImageView.image = UIImage(data: imgName)
}
I would extend Data and create an image property to return an optional image from your data object:
extension Data {
var image: UIImage? { UIImage(data: self) }
}
Usage:
saveImageView.image = animal?.img?.image
This would allow you to provide a default image as well in case of nil using the nil coalescing operator:
saveImageView.image = animal?.img?.image ?? UIImage(named: "defaultImage")
guard let and if let are effectively the same thing. In fact, if you are guarding the first statement to check if it's nil anyway, you can combine them into a single check and do something like this:
guard let animalImageData = animal?.img,
let animalImage = UIImage(data: animalImageData) else { return }
saveImageView.image = animalImage
or alternatively,
if let animalImageData = animal?.img, let animalImage = UIImage(data: animalImageData) {
saveImageView.image = animalImage
}
When you force-unwrap animal!.img!, it is unsafe practice and should generally be avoided since it could lead to fatal exceptions.

what's the meaning of optional binding in swift

without optional binding,we use optional like this,it seems tedious
func doSomething(str: String?)
{
let v: String! = str
if v != nil
{
// use v to do something
}
}
with optional binding,it seems the if let doesn't do any thing to make it less tedious. we still have a if statement to test before use it.
func doSomething(str: String?)
{
if let v = str
{
// use v to do something
}
}
Is there any other examples can show some benefits to use optional bindings ?
Advantages of the Optional binding over If Statements and Forced Unwrapping:
local variable which is not an optional and a shortcut when a structure is deeper than one level
Context:
You have three techniques to work with an optionals:
Optional binding
If statements and forced unwrapping
Optional chaining
Optional binding
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. Optional binding can be used with if and while statements
to check for a value inside an optional, and to extract that value
into a constant or variable, as part of a single action.
If Statements and Forced Unwrapping
You can use an if statement to find out whether an optional contains a
value by comparing the optional against nil. You perform this
comparison with the “equal to” operator (==) or the “not equal to”
operator (!=).
Optional chaining
Optional chaining is a process for querying and calling properties,
methods, and subscripts on an optional that might currently be nil. If
the optional contains a value, the property, method, or subscript call
succeeds; if the optional is nil, the property, method, or subscript
call returns nil. Multiple queries can be chained together, and the
entire chain fails gracefully if any link in the chain is nil.
source
struct Computer {
let keyboard: Keyboard?
}
struct Keyboard {
let battery: Battery?
}
struct Battery {
let price: Int?
}
let appleComputer: Computer? = Computer(keyboard: Keyboard(battery: Battery(price: 10)))
func getBatteryPriceWithOptionalBinding() -> Int {
if let computer = appleComputer {
if let keyboard = computer.keyboard {
if let battery = keyboard.battery {
if let batteryPrice = battery.price {
print(batteryPrice)
return batteryPrice
}
}
}
}
return 0
}
func getBatteryPriceWithIfStatementsAndForcedUnwrapping() -> Int {
if appleComputer != nil {
if appleComputer!.keyboard != nil {
if appleComputer!.keyboard!.battery != nil {
if appleComputer!.keyboard!.battery!.price != nil {
print(appleComputer!.keyboard!.battery!.price!)
return appleComputer!.keyboard!.battery!.price!
}
}
}
}
return 0
}
func getBatteryPriceWithOptionalChainingAndForcedUnwrapping() -> Int {
if appleComputer?.keyboard?.battery?.price != nil {
print(appleComputer!.keyboard!.battery!.price!)
return appleComputer!.keyboard!.battery!.price!
}
return 0
}
func getBatteryPriceWithOptionalChainingAndOptionalBinding() -> Int {
if let price = appleComputer?.keyboard?.battery?.price {
print(price)
return price
}
return 0
}
func getBatteryPriceWithOptionalChainingAndNilCoalescing() -> Int {
print(appleComputer?.keyboard?.battery?.price ?? 0)
return appleComputer?.keyboard?.battery?.price ?? 0
}

How to create an elegant guard / return statement in Swift that gives output?

For example, this works:
guard condition == true else { return }
Which is fine, but creates a silent failure. What would be nice is to have a static function that could output feedback whilst also returning. Something like:
guard condition == true else { stop("condition was false") }
Am I living in dreamland here, or might this be possible?
Of course, I recognise that the following is possible:
guard condition == true else {
print("condition was false")
return
}
But is boilerplate heavy and kind of ugly. I have guard statements everywhere, this sort of code is: 1. useful; but 2. would bulk out my code by, like, 10% minimum.
It's utopian of me I know, but I would prefer an elegant solution. Anyone?
Use precondition instead of guard:
func test() {
precondition(yourCondition, "This is an error message")
//rest of your function
}
If yourCondition is false, the scope is going to be exited and the error message will be printed.
It really depends on what your function is all about. Typically methods with guard statements either have no return value or return optionals.
func myReturn() -> String? {
guard condition else { return nil }
}
if you want an analogue of stop, well, you could throw an Error, or even a fatalError
func myReturn() throws -> String {
guard condition else {
throw BadConditionError
}
}
Or
func myReturn() -> String {
guard condition else {
fatalError("Bad condition")
}
}
guard is an early exit mechanism, it prevents your program from getting into invalid state, use it accordingly. I'd also recommend reading on defer mechanism.
As I understand, you want to produce some output or show message on false condition or on nil value, before return in guard.
Below is my thinking:
func checkForNil(value: Any?) -> Any?
{
if value == nil
{
//showMessage("Nil value...")
print("nil value")
}
return value
}
you can use this as below:
guard let obj = self.checkForNil(value: objLoggedInUser) else { return}
Try using closures to get it working, i.e.
func func1(handler: ((String)->())) {
let condition = (2 == 2)
guard condition == true else {
handler("condition was false")
return
}
}
func1 { (reason) in
print(reason)
}

Initializer for conditional binding must have optional type

I don't understand why I am getting this error. I don't see anything wrong with the code. Please help!! Thanks!
guard reason == .completed else { return }
***guard let symptomTrackerViewController = symptomTrackerViewController***,
let event = symptomTrackerViewController.lastSelectedAssessmentEvent else { return }
let carePlanResult=carePlanStoreManager.buildCarePlanResultFrom(taskResult: taskViewController.result)
carePlanStoreManager.store.update(event, with: carePlanResult, state: .completed) {
success, _, error in
if !success {
print(error?.localizedDescription)
}
}
}
}
The syntax
if let someVal = someVal {
//Inside the braces someVal is unwrapped
}
Works when the variable name is the same on both sides of the equal sign.
However, the code
guard let someVal = someVal else {
return
}
Is not legal. I believe the reason the first form, the if let conditional binding, lets you unwrap an optional using the same name, is that the reassignment is only valid inside the inner level of scope created by the braces for the if statement.
In contrast, the guard statement does not put braces around the code that gets executed when the optional unwrapping succeeds. There's no inner scope in which the new definition of someVar is valid.
The second part of it is that it sounds like your symptomTrackerViewController is not an optional. If symptomTrackerViewController is not an optional then any code that attempts to unwrap it (if let, guard let, and using ? and !) will fail.
Either symptomTrackerViewController or symptomTrackerViewController.lastSelectedAssessmentEvent is not an optional.
Check if your symptomTrackerViewController is optional. If it is not optional it can never be nil so you can remove it from the guard.
Try changing guard to
guard let symptomTrackerVC = symptomTrackerViewController,
let event = symptomTrackerViewController.lastSelectedAssessmentEvent
else { return }
When you are using if let something = somethingElse,
somethingElse must be an optional.

Resources