How to Unwrap Swift Optionals

How do you unwrap Swift optionals?

There are many similarities and just a handful of differences.

(Regular) Optionals

  • Declaration: var opt: Type?

  • Unsafely unwrapping: let x = opt!.property // error if opt is nil

  • Safely testing existence : if opt != nil { ... someFunc(opt!) ... } // no error

  • Safely unwrapping via binding: if let x = opt { ... someFunc(x) ... } // no error

  • Safely chaining: var x = opt?.property // x is also Optional, by extension

  • Safely coalescing nil values: var x = opt ?? nonOpt

Implicitly Unwrapped Optionals

  • Declaration: var opt: Type!

  • Unsafely unwrapping (implicit): let x = opt.property // error if opt is nil

    • Unsafely unwrapping via assignment:

      let nonOpt: Type = opt // error if opt is nil

    • Unsafely unwrapping via parameter passing:

      func someFunc(nonOpt: Type) ...
      someFunc(opt) // error if opt is nil

  • Safely testing existence: if opt != nil { ... someFunc(opt) ... } // no error

  • Safely chaining: var x = opt?.property // x is also Optional, by extension

  • Safely coalescing nil values: var x = opt ?? nonOpt

how to unwrap swift optionals within a struct

You cannot directly set a property of an Optional if you haven't assigned a value to the Optional itself, since due to the optional chaining, the setter of usd won't be called.

Instead, you need to assign a Price to purchase.price.

var purchase:Item = Item()
purchase.name = "lampshade"
purchase.price = Price(USD: 19.2)

Or if you want to assign an "empty" price, then optional chaining on that works, since now price is not nil.

var purchase:Item = Item()
purchase.name = "lampshade"
purchase.price = Price()
purchase.price?.USD = 19.2

Also, you should try to make properties immutable immutable (let) by default and only make properties mutable (var) if they really need to change after initialisation. You should also only add default values to properties where it makes sense for them to have a default value. For instance, name shouldn't have one, but rather, should be immutable with its value being set in the init.

struct Item{
let name: String
var price: Price?
}


var purchase = Item(name: "lampshade")
purchase.price = Price(USD: 19.2)

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"
}

How to unwrap double optionals in switch statement -- Swift

This works, but I'm curious why I need to double unwrap the default
case? I was thinking once the lhs and rhs get unwrapped from the
switch inputs, the default case would work with a Bool? for lhs and
rhs.

You aren't changing lhs and rhs when you do lhs ?? nil and rhs ?? nil. You are creating new values. So when you get to the default case, lhs and rhs are still Bool??. You can use let lhs and let rhs to capture the unwrapped values as I did in my solution below.


Here is another way to do it. A bit cleaner with some pattern matching:

switch (lhs as? Bool, rhs as? Bool) {
case (nil, nil):
return nil
case (nil, let rhs):
return rhs
case (let lhs, nil):
return lhs
case (let lhs?, let rhs?):
return lhs || rhs
}

Explanation

Casting the Bool?? with as? Bool leaves you with a Bool?. The let rhs and let lhs in the pattern matching catch the Bool? value so that it can be returned. In the final case, let lhs? and let rhs? unwrap the Bool? values to get Bool values so that || can be performed.


Test Cases

test(nil, nil) // nil
test(nil, false) // false
test(nil, true) // true
test(nil, Optional(nil)) // nil
test(nil, Optional(false)) // false
test(nil, Optional(true)) // true

test(false, nil) // false
test(false, false) // false
test(false, true) // true
test(false, Optional(nil)) // false
test(false, Optional(false)) // false
test(false, Optional(true)) // true

test(true, nil) // true
test(true, false) // true
test(true, true) // true
test(true, Optional(nil)) // true
test(true, Optional(false)) // true
test(true, Optional(true)) // true

test(Optional(nil), nil) // nil
test(Optional(nil), false) // false
test(Optional(nil), true) // true
test(Optional(nil), Optional(nil)) // nil
test(Optional(nil), Optional(false)) // false
test(Optional(nil), Optional(true)) // true

test(Optional(false), nil) // false
test(Optional(false), false) // false
test(Optional(false), true) // true
test(Optional(false), Optional(nil)) // false
test(Optional(false), Optional(false)) // false
test(Optional(false), Optional(true)) // true

test(Optional(true), nil) // true
test(Optional(true), false) // true
test(Optional(true), true) // true
test(Optional(true), Optional(nil)) // true
test(Optional(true), Optional(false)) // true
test(Optional(true), Optional(true)) // true

Unwrapping Optional types in a Generic method in Swift 5

If the intention is to call unwrap() only with key paths leading to an optional property then you can declare the argument type as KeyPath<A, T?>, and the second placeholder type U is not needed:

func unwrap<T>(_ path: KeyPath<A, T?>) throws -> T {
if let value = self[keyPath: path] {
return value
} else {
throw Error.missing("KeyPath '\(path)' is 'nil'")
}
}

The usage can be simplified to

func convert() throws -> B {
let name = try unwrap(\.name)
return B(name: name)
}

or just

func convert() throws -> B {
return try B(name: unwrap(\.name))
}

How can I safety unwrap an optional with a default value using `coalescing unwrapping` - Not workig - Swift

You can unwrap the access of the array and the conversion to Double

let pounds = Double(arrayOfStrings.first ?? "0") ?? 0.0

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)
}

How to unwrap an optional list of type Images in SwiftUI

The simplest direct answer to this is to use the same ?? operator you mentioned to provide an empty array:

struct ContentView : View {
@State var list_of_images : [Image]? = nil

var body: some View {
ForEach(0..<(list_of_images ?? []).count) {
list_of_images?[$0]
.resizable()
.aspectRatio(contentMode: .fill)
}
}
}

However, I'd be concerned in general about the idea of storing the Images themselves. I might look into another way of storing references to them (paths? names? UIImages?) that could be iterated over instead. In that case, you could do something like this:

struct ContentView : View {
@State var imageNames : [String]? = nil

var body: some View {
ForEach(imageNames ?? [], id: \.self) { imageName in
Image(imageName)
.resizable()
.aspectRatio(contentMode: .fill)
}
}
}

This approach doesn't work with the array of Images because Image doesn't conform to Identifiable

Update, based on comments:

struct ContentView : View {
@State var images : [UIImage]? = nil

var body: some View {
ForEach(images ?? [], id: \.self) { image in
Image(uiImage: image)
.resizable()
.aspectRatio(contentMode: .fill)
}
}
}

Unwrapping optional in SwiftUI View

There are two ways to work with optionals in this context:

The first one, if you don't want this view to show at all if profile.height is nil:

profile.height.map({ TagBox(field: "height", value: String($0))})

The second one, if you want this view to show, but with a default value instead:

TagBox(field: "height", value: String(profile.height ?? 0))


Related Topics



Leave a reply



Submit