Function throws AND returns optional.. possible to conditionally unwrap in one line?
Update: As of Swift 5, try?
applied to an optional expression does not add another level of optionality, so that a “simple” optional binding is sufficient. It succeeds if the function did not throw an error and did not return nil
. val
is then bound to the unwrapped result:
if let val = try? getSomething() {
// ...
}
(Previous answer for Swift ≤ 4:) If a function throws and returns an optional
func getSomething() throws -> Value? { ... }
then try? getSomething()
returns a "double optional" of the
type Value??
and you have to unwrap twice:
if let optval = try? getSomething(), let val = optval {
}
Here the first binding let optval = ...
succeeds if the function did
not throw, and the second binding let val = optval
succeeds
if the return value is not nil
.
This can be shortened with case let
pattern matching to
if case let val?? = try? getSomething() {
}
where val??
is a shortcut for .some(.some(val))
.
Unwrap optional in inline conditional statement
Yes. map
can be applied to an optional and the closure is executed if the value is not nil
, otherwise nil
is returned. You can then use the nil coalescing operator to unwrap the result of map
:
let a = b.map { f($0) } ?? false
As @Alexandar noted in the comments, since your function takes just one argument, you can write map
just passing the function name:
let a = b.map(f) ?? false
Complete example
func f(_ a:Int) -> Bool {
return a > 17
}
var b:Int? = 18
let a1 = b.map(f) ?? false
print(a1) // true
b = nil
let a2 = b.map(f) ?? false
print(a2) // false
func map<U>(_ transform: (Wrapped) throws -> U) rethrows -> U?
Description
Evaluates the given closure when this Optional instance is not nil, passing the unwrapped value as a parameter.
Use the map method with a closure that returns a nonoptional value.
Using nil-coalescing operator with try? for function that throws and returns optional
Essentially, this has to do with the grammar of the try
operator. When used with a binary expression without brackets, try
applies to the whole binary expression, so this:
try? machine.itemCode(code: 0) ?? "Unknown"
is the same as:
try? (machine.itemCode(code: 0) ?? "Unknown")
Since itemCode
throws an error, the latter part of the expression ?? "Unknown
is ignored, and the try?
expression evaluates to nil.
On the other hand, the second expression is like this:
(try? machine.itemCode(code: 0)) ?? "Unknown"
The try?
expression is evaluated first (to nil), then the ??
is applied, evaluating the whole expression to "Unknown".
Swift 3 better way to unwrap an optional
The proper way would be:
if let msg = myStr.removingPercentEncoding {
print("msg \(msg)")
}
Here, msg
is only valid inside the if
statement and only if myStr.removingPercentEncoding
isn't nil
.
If you wish to print something if myStr.removingPercentEncoding
is nil
, then you could and an else
:
if let msg = myStr.removingPercentEncoding {
print("msg \(msg)")
} else {
print("msg has no value")
}
Read up on Optional Binding in the The Swift Programming Language book.
How can I use try with the coalescing operator?
A single try
can cover the entire expression, so you can just say:
guard let x = try f("a") ?? f("b") ?? f("c") else {
print("Couldn't get a valid value for x")
return
}
Same goes for try?
:
guard let x = try? f("a") ?? f("b") ?? f("c") else {
print("Couldn't get a valid value for x")
return
}
Although note that in Swift 4.2 x
will be String?
due to the fact that you're applying try?
to an already optional value, giving you a doubly-wrapped optional which guard let
will only unwrap one layer of.
To remedy this, you could coalesce to nil
:
guard let x = (try? f("a") ?? f("b") ?? f("c")) ?? nil else {
print("Couldn't get a valid value for x")
return
}
But in Swift 5 this is unnecessary due to SE-0230, where try? f("a") ?? f("b") ?? f("c")
will be flattened into a single optional value automatically by the compiler.
Map with Optional Unwrapping in Swift
compactMap()
can do this for you in one step:
let paths:[String?] = ["test", nil, "Two"]
let nonOptionals = paths.compactMap{$0}
nonOptionals
will now be a String array containing ["test", "Two"]
.
Previously flatMap()
was the proper solution, but has been deprecated for this purpose in Swift 4.1
Force passing a single parameter, to a multi-optional-parameter function
You want something of Swift it can not do. In particular, as far as I am aware, Swift has no union types. If you want something like that, you may want to look at functional or functional-inspired languages.
Interlude: If both types are structs or classes (i.e. not protocols) you can use this pattern:
protocol DataOrError {}
extension Data: DataOrError {}
extension ErrorClass: DataOrError {}
Assuming there are no other implementers of the protocol, you now have something that approaches a union type. You can switch-case over types, too:
switch doe {
case let e as Error: print("Error \(e)")
case let d as Data: print("Data \(d)")
default: assert(false, "thou shalt not implement my dummy!")
}
Anyway, we can fake it using enumerations with associated values, which can (with limitations) be used as a poor-man's union type.
Define an enum like this:
enum DataOrError {
case Data(Data)
case Error(Error)
}
Now you can use DataOrError
whereever you would like to, well, have (exactly) one of Data
or Error
, and DataOrError?
for at most one.
Caller-site, you get something like this:
extension String: Error {} // low-effort errors, don't judge me!
func myFunction(completion: (DataOrError) -> ()) {
completion(.Data(Data(bytes: [1,2,3,4,5])))
completion(.Error("this won't work!"))
}
And callee-site:
var myCompletion = { (doe: DataOrError) in
switch doe {
case .Data(let d): print("Data \(d)")
case .Error(let e): print("Error \(e)")
}
}
myFunction(completion: myCompletion)
// > Data 5 bytes
// > Error this won't work!
Design note: You may be looking for a generalization in another direction, especially if you have many different types you want to OR with Error
. In that case, an explicit wrapper may be a good solution, even though you sacrifice nice syntax.
struct TalkativeOptional<T> {
private(set) var rawValue: T?
private(set) var error: Error?
init(value: T) {
self.rawValue = value
}
init(error: Error) {
self.error = error
}
}
Note how exactly one of the two properties can be non-nil
. There are two more combinations; here, you can control which you want by your choice of initializers.
Example callee-site:
func convert(_ number: String) -> TalkativeOptional<Int> {
if let int = Int(number) {
return TalkativeOptional(value: int)
} else {
return TalkativeOptional(error: "'\(number)' not a valid integer!")
}
}
Example caller-site:
var a = convert("dsa2e2")
if let val = a.rawValue {
print("a + 1 = \(val + 1)")
} else { // we know by design that error is set!
print("ERROR: \(a.error!)")
}
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"
}
Related Topics
How to Tell If a Node Is on the Screen Spritekit Swift
Draw a Hole in a Rectangle with Spritekit
Mutable Binding in Swiftui Live Preview
What Does "Get" Mean in a Protocol's Property Declaration
How to Create an Array of Functions
Swift Parsing Attribute Name for Given Elementname
How to Open Settings App Programmatically
Working with C Strings in Swift, Or: How to Convert Unsafepointer<Cchar> to Cstring
How to Check the Network Speed Using Swift
Background Request Not Execute Alamofire Swift
Set the Size and Position of All Windows on the Screen in Swift
Rxswift Merge Different Kind of Observables
Can an Enum Contain Another Enum Values in Swift
Parameters After Opening Bracket
How to Create Several Cached Uicolor
Segue from a Slpagingviewswift Vc and Dismiss the Destination Vc to Return
Firebase Storage Downloadurl()' Is Deprecated: Use 'Storagereference.Downloadurlwithcompletion()