Swift 5's Result Type
Want more? Subscribers can view all 470 episodes. New episodes are released regularly.
Episode Link:
- FileManager.contentsOfDirectory - This is an example that shows the difference between Obj-C style
NSError**
style of reporting errors versus Swift'sthrows
.
Example of an Asynchronous API
Using Result type of Swift 5, we'll handle the errors in asynchronous APIs.
We'll create an example of an asynchronous API with completion block and escaping closure having optional return values. Apart from the success and failure case, the function may receive both the optional values or they may be nil or empty.
```swift receive a success or
func getMessage(completion: @escaping (String?, Error?) -> Void) {
}
```
Using Result Type for Success
Using the Result type, we'll specify the return value received in the case of success and failure.
func getMessage(completion: @escaping (Result<String, Error>) -> Void) {
completion(.success("It worked!"))
}
getMessage { result in
switch result {
case .success(let msg):
print(String(msg))
case .failure(let error):
print(error)
}
}
Using Result type for Failure
In case of failure, the type of returned value Error
is needed to match the error type that conformance to the Error protocol.
struct MyCustomError : Error { }
func getMessageThatFails(completion: @escaping (Result<String, Error>) -> Void) {
completion(.failure(MyCustomError()))
}
getMessageThatFails { result in
switch result {
case .success(let msg):
print(msg)
case .failure(let error):
print(error)
}
}
Transforming a Result Using Map
Result
enables the transformation of success value into a new result. We'll use the map
method that returns a new result by mapping any success value using the given transformation.
getMessage { result in
let newResult = result.map { $0.reversed() }
switch newResult2 {
case .success(let msg):
print(String(msg))
case .failure(let error):
print(error)
}
}
Now we'll transform the error by mapping it to another custom error.
struct MyOtherError : Error { }
getMessage { result in
let newResult = result
.map { $0.reversed() }
.mapError { _ in MyOtherError() }
switch newResult {
case .success(let msg):
print(String(msg))
case .failure(let error):
print(error)
}
}
Note that this changes the type of Result
we are working with.
Transforming a Result Using FlatMap
Another way of transforming a Result is by using flatMap
. This allows us to convert a successful value into an Error. In this example, imagine that we want to throw an error if the message has an odd number of characters.
struct OddNumberOfCharacters : Error { }
getMessage { result in
let newResult = result
.map { $0.reversed() }
// MAP Result<T, E> T->U --> Result<U, E>
// flatMap -> Result<U, E>
// Result<T, E> T->Result<U, E> --> Result<U, E>
let newResult2 = newResult.flatMap { msg -> Result<String, Error> in
if msg.count % 2 == 0 {
return .success(String(msg))
} else {
return .failure(OddNumberOfCharacters())
}
}
}
Convert a Result to Throw an Expression
We can convert a result type into a throwing style by using get()
:
let r: Result<Int, Error> = .success(52)
do {
try r.get()
} catch {
// ...
}
We can use get
along with try?
to only return the success value instead of the exception.
let r: Result<Int, Error> = .success(52)
let x = try? r.get()
gaurd let x = try? r.get() else { return}
if let x = try? r.get() {
}
// or if we are absolutely
try! r.get()
Chaining the Result of an Operation Into Another
To convert an input of an operation into another, we'll create 3 operations returning the success and the custom errors. We'll use flatMap
to convert the result of one operation as an input of the next operation.
enum CustomErrors : String, Error, CustomStringConvertible {
case operation1Failed
case operation2Failed
case operation3Failed
var description: String {
return "?? \(self.rawValue)"
}
}
func operation1() -> Result<Int, CustomErrors> {
print("Running operation 1")
let random = Int.random(in: 1...10)
if random < 3 {
return .failure(.operation1Failed)
}
return .success(random)
}
func operation2(_ input: Int) -> Result<String, CustomErrors> {
print("Running operation 2")
if Bool.random() {
return .failure(.operation2Failed)
} else {
var s = ""
for _ in 1...input {
s.append("??")
}
return .success(s)
}
}
func operation3(_ input: String) -> Result<String, CustomErrors> {
print("Running operation 3")
if Bool.random() {
return .failure(.operation3Failed)
} else {
return .success(String(input.map { _ in "????" }))
}
}
func run() -> Result<String, CustomErrors> {
let r = operation1()
.flatMap { operation2($0) }
.flatMap { operation3($0) }
return r
}
print(run())
print("-------------------------")
print(run())
print("-------------------------")
print(run())
By running these operations, we can use the output of an operation into another. Here we've added some random failures so we can see how it works.