Why Use Optional Binding

Why use optional binding?

You use optional binding (if let) if you have a block of code that you only want to run if the variable is not nil.

You use optional chaining (the ?) only when accessing the properties/methods of an optional variable.

But there are situations where optional chaining is not possible (i.e. you're not accessing a property/method of the optional variable, but rather using that variable for other purposes). For example

// let's assume `data` is a `NSData?` optional

if let imageData = data {
let image = UIImage(data: imageData)

// now do something with `image`
}

We do this because in this context, we can't use optional chaining, and using forced unwrapping (e.g. let image = UIImage(data: data!)) would crash if data was nil.

How is optional binding used in swift?

First someOptional is checked to see if it's nil or has data. If it's nil, the if-statement just doesn't get executed. If there's data, the data gets unwrapped and assigned to constantName for the scope of the if-statement. Then the code inside the braces is executed.

if let constantName = someOptional {
statements
}

There's no way to chain this functionality in one if-statement. let constantName = someOptional does not directly evaluate to a boolean. It's best to think of "if let" as a special keyword.

what's the meaning of optional binding in swift

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
}

Optional Binding, what exactly the word Binding means here?

Does the Binding mean the action of assigning the valid value into the temporary constant/variable? I.e. "binding" those two things together?

Yes. Basically, assignment of a value to a variable name is a binding — it "binds" the name to the value. So even this is a binding:

let x = 1

What's special with if let is that the binding takes place only if the value is an Optional that can be safely unwrapped (that is, it is not nil). If it can't be unwrapped safely, it is not unwrapped and no binding takes place (and the if condition fails).

OR condition for optional binding?

Unfortunately I don't think this is possible in a single line (like if let x = y || let z = a {}). Logically it wouldn't make sense, because you would end up in a state where both variables would remain optional (if either is unwrapped, you don't know which is unwrapped or if both are). I think you'll need to take some other approach to this problem. I think the simplest form would be something like

if let unwrappedProperty1 = property1 {
handleProperty(unwrappedProperty1)
} else if let unwrappedProperty2 = property2 {
handleProperty(unwrappedProperty2)
}

or you could do something like

if let unwrappedProperty = property1 ?? property2 {
handleProperty(unwrappedProperty)
}

which would give priority to property1

Swift optional binding in while loop

It's the same

while let someValue = someOptional
{
doSomethingThatmightAffectSomeOptional(with: someValue)
}

Here is a concrete example of iterating a linked list.

class ListNode
{
var value: String
var next: ListNode?

init(_ value: String, _ tail: ListNode?)
{
self.value = value
self.next = tail
}
}

let list = ListNode("foo", ListNode("bar", nil))

var currentNode: ListNode? = list
while let thisNode = currentNode
{
print(thisNode.value)
currentNode = thisNode.next
}

// prints foo and then bar and then stops

Using optional binding when SwiftUI says no

I think you should change approach, the control of saving should remain inside the model, in the view you should catch just the new name and intercept the save button coming from the user:

Sample Image

class AshwagandhaVM: ObservableObject {
@Published var currentFoo: Foo?

init() {
self.currentFoo = Foo.sampleData.first
}
func saveCurrentName(_ name: String) {
if currentFoo == nil {
Foo.sampleData.append(Foo(name: name))
self.currentFoo = Foo.sampleData.first(where: {$0.name == name})
}
else {
self.currentFoo?.name = name
}
}
}

struct ContentView: View {
@StateObject var ashwagandhaVM = AshwagandhaVM()
@State private var textInput = ""
@State private var showingConfirmation = false

var body: some View {
VStack {
TextField("", text: $textInput)
.padding()
.textFieldStyle(.roundedBorder)
Button("save") {
showingConfirmation = true
}
.padding()
.buttonStyle(.bordered)
.controlSize(.large)
.tint(.green)
.confirmationDialog("are you sure?", isPresented: $showingConfirmation, titleVisibility: .visible) {
Button("Yes") {
confirmAndSave()
}
Button("No", role: .cancel) { }
}
//just to check
if let name = ashwagandhaVM.currentFoo?.name {
Text("in model: \(name)")
.font(.largeTitle)
}
}
.onAppear() {
textInput = ashwagandhaVM.currentFoo?.name ?? "default"
}
}

func confirmAndSave() {
ashwagandhaVM.saveCurrentName(textInput)
}
}

UPDATE

do it with whole struct

struct ContentView: View {
@StateObject var ashwagandhaVM = AshwagandhaVM()
@State private var modelInput = Foo(name: "input")
@State private var showingConfirmation = false

var body: some View {
VStack {
TextField("", text: $modelInput.name)
.padding()
.textFieldStyle(.roundedBorder)
Button("save") {
showingConfirmation = true
}
.padding()
.buttonStyle(.bordered)
.controlSize(.large)
.tint(.green)
.confirmationDialog("are you sure?", isPresented: $showingConfirmation, titleVisibility: .visible) {
Button("Yes") {
confirmAndSave()
}
Button("No", role: .cancel) { }
}
//just to check
if let name = ashwagandhaVM.currentFoo?.name {
Text("in model: \(name)")
.font(.largeTitle)
}
}
.onAppear() {
modelInput = ashwagandhaVM.currentFoo ?? Foo(name: "input")
}
}

func confirmAndSave() {
ashwagandhaVM.saveCurrentName(modelInput.name)
}
}


Related Topics



Leave a reply



Submit