Swift Optional of Optional

How to unwrap double optionals?

Given a double optional such as this doubly wrapped String:

let a: String?? = "hello"
print(a as Any) // "Optional(Optional("hello"))\n"

@Leo, showed that you could use optional binding twice:

if let temp = a, let value = temp {
print(value) // "hello\n"
}

or force unwrap twice:

print(value!!)  // don't do this - you're just asking for a crash

Here are 5 more methods you can use to safely unwrap a double optional:

Method 1:

You can also use pattern matching:

if case let value?? = a {
print(value) // "hello\n"
}

As @netigger noted in their answer, this can also be written as:

if case .some(.some(let value)) = a {
print(value) // "hello\n"
}

which while less concise might be a bit easier to read.


Method 2:

Alternatively, you can use the nil coalescing operator ?? twice:

print((a ?? "") ?? "")  // "hello\n"

Note: Unlike the other methods presented here, this will always produce a value. "" (empty String) is used if either of the optionals is nil.


Method 3:

Or you can use the nil coalescing operator ?? with optional binding:

if let value = a ?? nil {
print(value) // "hello\n"
}

How does this work?

With a doubly wrapped optional, the value held by the variable could be one of 3 things: Optional(Optional("some string")), Optional(nil) if the inner optional is nil, or nil if the outer optional is nil. So a ?? nil unwraps the outer optional. If the outer optional is nil, then ?? replaces it with the default value of nil. If a is Optional(nil), then ?? will unwrap the outer optional leaving nil. At this point you will have a String? that is nil if either the inner or outer optional is nil. If there is a String inside, you get Optional("some string").

Finally, the optional binding (if let) unwraps Optional("some string") to get "some string" or the optional binding fails if either of the optionals is nil and skips the block.


Method 4:

Also, you can use flatMap with optional binding:

if let value = a.flatMap({ $0 }) {
print(value) // "hello\n"
}

Method 5:

Conditionally cast the value to the type. Surprisingly, this will remove all levels of optionals:

let a: String?? = "hello"
let b: String??????? = "bye"

if let value = a as? String {
print(value) // "hello\n"
}

print(b as Any) // "Optional(Optional(Optional(Optional(Optional(Optional(Optional("bye")))))))\n"

if let value = b as? String {
print(value) // "bye\n"
}

What's the Purpose of an Optional of an Optional in Swift Optional Types?

These occur sometimes in Swift when you are accessing data.

One example is if you have a dictionary with an optional type for the value, and then you look up a value in that dictionary:

let rockStars: [String: String?] = ["Sting": nil, "Elvis": "Presley", "Bono": nil, "Madonna": nil]

let lastName = rockStars["Elvis"]
print(lastName as Any)
Optional(Optional("Presley"))

This happens because a dictionary look up can fail when the key is not present, and it returns nil in that case. So the return type of a dictionary lookup has to be the value type wrapped in an optional. A look up from the rockStars dictionary returns a String??.

Why does Swift allow double optionals?

I'm going to try and answer this from a Swift perspective, so forgive me if my FP terminology is incorrect.

Why would I ever use double optionals?

There is one well known case for double optional. A collection that uses optional to indicate no value at this key, which is also storing values which might be optional. Here it might be important to know whether the collection or the element returned the nil.

let possibleNumbers: [String: Int?] = [:]
guard let possibleNumber = possibleNumbers["one"] else {
print("No number stored")
}
guard let number = possibleNumber else {
print("Number at key was nil")
}

Can we automatically convert T??????? to T??

No, but it can be achieved with flatMap. Although you need to add a new map for each level of optional.

possibleNumbers["one"].flatMap { $0 }

Also I do not believe the Swift Optional is ever explicitly called a monad. It is implemented as a generic enum internally and it follows the semantics of that type more closely. At least I cannot find any reference in the language guide https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/TheBasics.html

Swift optionals and if let statement

When you do

if let num = Int(numTextField.text!){}

It will unwrap the value for you and check if it can set the value of textfield to num. If the value is nil you will be able to handle the error like this

if let num = Int(numTextField.text!){
print("Yes the value is not nil")
}else{
print("Couldn't assign value to num because it's nil")
}

If you do

var num = Int(numTextField.text!)!

and the textfield is nil, you will get a runtime error and your app will crash.

Unwrapping an Optional value in swift and realm

As from comments if appears that your view is not yet loaded and some of your views are still nil. Your app crashes because in line limitLabel.text = limit[0].limitSum the limitLabel is nil. It would crash regardless of Realm even by calling limitLabel.text = "Hello world!"

You can always guard data that you need to avoid changes in your code. Simply add

guard let limitLabel = limitLabel else { return nil } 
guard let ForThePeriod = ForThePeriod else { return nil }

and so on.

I tried to clean up your code a bit. It is hard to understand what exactly are you trying to achieve but something like the following may seem a bit more appropriate:

func leftLabels() {
// Elements needed for method to execute.
guard let limitLabel = limitLabel else { return }
guard let forThePeriodLabel = forThePeriodLabel else { return }
guard let availableForSpendingLabel = availableForSpendingLabel else { return }

// Items that will be reused throughout the method later on
let limits: [Limit]
let firstLimit: Limit
let dates: (start: Date?, end: Date?)
let filterLimit: Int

limits = self.realm.objects(Limit.self)
guard limits.isEmpty == false else { return }
firstLimit = limits[0]

// limitLabel
limitLabel.text = firstLimit.limitSum

// Date components
dates = {
let calendar = Calendar.current
let formatter = DateFormatter()
formatter.dateFormat = "yyyy/MM/dd HH:mm"

let firstDay = firstLimit.limitDate as Date
let lastDay = firstLimit.limitLastDate as Date

let firstComponent = calendar.dateComponents([.year, .month, .day], from: firstDay)
let lastComponent = calendar.dateComponents([.year, .month, .day], from: lastDay)

let startDate = formatter.date(from: "\(firstComponent.year!)/\(firstComponent.month!)/\(firstComponent.day!) 00:00")
let endDate = formatter.date(from: "\(lastComponent.year!)/\(lastComponent.month!)/\(lastComponent.day!) 23:59")

return (startDate, endDate)
}()

// forThePeriodLabel
filterLimit = realm.objects(SpendingDB.self).filter("self.date >= %@ && self.date <= %@", startDate ?? "", endDate ?? "").sum(ofProperty: "cost")
forThePeriodLabel.text = String(filterLimit)

// availableForSpendingLabel
availableForSpendingLabel.text = {
guard let a = Int(firstLimit.limitSum) else { return "" }
let b = filterLimit
let c = a - b
return String(c)
}()
}

Note some practices which help you better to structure and solve your code.

  • Guard dangerous data at first
  • Create a list of reusable items for your method (there should be as fewer as possible, in most cases none). Note how these can be later assigned to. And if you try using it before assigning to it, you will be warned by your compiler.
  • Wrap as much code into closed sections such as availableForSpendingLabel.text = { ... code here ... }()
  • Use tuples such as let dates: (start: Date?, end: Date?)
  • Don't be afraid of using long names such as availableForSpendingLabel

I would even further try and break this down into multiple methods. But I am not sure what this method does and assume that you have posted only part of it...

========== EDIT: Adding alternate approach ==========

From comments this is a financial application so probably at least dealing with Decimal numbers would make sense. Also introducing approach with adding a new structure which resolves data internally. A formatter is also used to format the number. And some other improvements:

struct Limit {
let amount: Decimal
let startDate: Date
let endDate: Date
}

struct Spending {
let cost: Decimal
let date: Date
}

struct LimitReport {
let limitAmount: Decimal
let spendingSum: Decimal
let balance: Decimal

init(limit: Limit) {
let limitAmount: Decimal = limit.amount
let spendingSum: Decimal = {
let calendar = Calendar.autoupdatingCurrent // Is this OK or should it be some UTC or something?
func beginningOfDate(_ date: Date) -> Date {
let components = calendar.dateComponents([.day, .month, .year], from: date)
return calendar.date(from: components)!
}
let startDate = beginningOfDate(limit.startDate)
let endDate = calendar.date(byAdding: .day, value: 1, to: startDate)

let spendings: [Spending] = realm.objects(Spending.self).filter { $0.date >= startDate && $0.date < endDate }
return spendings.reduce(0, { $0 + $1.cost })
}()
let balance = limitAmount - spendingSum

self.limitAmount = limitAmount
self.spendingSum = spendingSum
self.balance = balance
}

}

func leftLabels() {
// Elements needed for method to execute.
guard let limitLabel = limitLabel else { return }
guard let forThePeriodLabel = forThePeriodLabel else { return }
guard let availableForSpendingLabel = availableForSpendingLabel else { return }

guard let limit = self.realm.objects(Limit.self).first else { return }

let formatter = NumberFormatter()
formatter.numberStyle = .currency
formatter.currencySymbol = "$"

let report = LimitReport(limit: limit)

limitLabel.text = formatter.string(from: report.limitAmount)
forThePeriodLabel.text = formatter.string(from: report.spendingSum)
availableForSpendingLabel.text = formatter.string(from: report.balance)
}

Getting a value of Optional(data value) even when unwrapping

When you access a dictionary it returns optional of it's value type because it doesn't know runtime whether certain key is available in the dictionary or not. If the key is present then it's value is returned but if it's not then we get nil value.

Use optional binding to access unwrapped value:

if let url = params["facebook_url"] {
print(url) // facebook url
}

In case double optional unwrapping:

if let urlOptional = params["facebook_url"], let value = urlOptional {
print(value) // facebook url
}

Note: Also check the source where you have set the value of 'facebook_url' key.

Swift Extension - Optional

What you're doing in your example will raise errors. It boils down to

let vm: MyViewModel
if let heading = dataSource?.heading {
_ = MyViewModel(heading.title, heading.subtitle)
vm = heading
}

So you're trying to assign heading to vm (which I am assuming are of different types) and you just construct and drop the MyViewModel you construct in the closure

What would be a better option is something along these lines:

func mapOptionalOrNot<T>(notNil: (Wrapped) -> T, isNil: () -> T) -> T {
switch self {
case .some(let value):
return notNil(value)
case .none:
return isNil()
}
}

And you could of course give both function default arguments so you can leave them out.

With Swift 5.3s new multi closures you could do something like

let vm: MyViewModel = dataSource?.heading.mapOptionalOrNot { value in
// Map if we have a value
return MyViewModel(title: value.title, subtitle: value.subtitle)
} isNil: {
// Map if we don't have a value
return MyViewModel(title: "Empty", subtitle: "Empty")
}

Swift: Can't unwrap an optional inside a loop

As explained in Why isn't guard let foo = foo valid?,

let name: String? = "Toto"
guard let name = name else { fatalError() }

is not valid because guard does not introduce a new scope, and you cannot have two variables with the same name in the same scope.

That this compiles on the file level (i.e. in “main.swift”) is a bug. Apparently the variable bound via guard hides the other variable of the same name, regardless of the type and the order in which they are declared:

let aName = "Toto"
guard let aName = Int("123") else { fatalError() }
print(aName) // 123

guard let bName = Int("123") else { fatalError() }
let bName = "Toto"
print(bName) // 123

This bug has been reported as SR-1804 No compiler error for redeclaration of variable bound by guard.

How to loop through an optional collection

You have a couple of options. You can provide an empty array as the default value for elements in a for ... in loop:

let elements: [Element]?

for element in elements ?? [] {

}

Or you can use forEach and optional chaining on elements

elements?.forEach { element in

}

Bear in mind that optional collections are an anti-pattern in Swift. You can represent the lack of value using an empty collection, so wrapping a collection in an optional doesn't provide any extra value, while complicating the interface.

Bridging Optional Binding to Non-Optional Child (SwiftUI)

Child should take a Binding to the non-optional string, rather than using @State, because you want it to share state with its parent:

struct Child: View {
// within Child, I'd like the value to be NonOptional
@Binding var text: String

var body: some View {
TextField("OK: ", text: $text).multilineTextAlignment(.center)
}
}

Binding has an initializer that converts a Binding<V?> to Binding<V>?, which you can use like this:

            if let binding = Binding<String>($model.name) {
Child(text: binding)
}

If you're getting crashes from that, it's a bug in SwiftUI, but you can work around it like this:

            if let text = model.name {
Child(text: Binding(
get: { model.name ?? text },
set: { model.name = $0 }
))
}


Related Topics



Leave a reply



Submit