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:
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
Swift3 Different Font in The All of The UIview with Localization Each
Call Function on App Termination in Swift
Swiching Between 2 Diferent Nsviewcontrollers with Data
Firebase Data Retrieval - Edit Annotation
Thread Safety of Method Calls on "Shared" Static Constant Property
Navigationview Inside a Tabview Swift UI
Uicollectionview Selected Cells Issue
Populating Collection View from Array in Document Directory
Heightanchor.Constraint Not Change Height of View
How Make Polygon Without Intersection in Swift
Skphysicscontact Not Detecting Categorybitmask Collision
How to Observe Torchlevel in Swift
Set Maximum Characters (To One) in a Nstextfield in Swift
Connecting Avaudiosourcenode to Avaudiosinknode Does Not Work