In error handling using the do-catch statement, ** Describe the process that may cause an error in the do clause, If an error occurs, it will move to the catch clause. ** **
In addition, since you can access the detailed information of the error in the catch clause, As with the Result \ <Success, Failure> type, processing using error details can be performed.
do-catch is excellent!
In the do-catch statement, describe the process that may cause an error due to the ** throw statement ** in the do clause. Describe error handling in the catch clause.
The throw statement is a statement that causes an error ** Use a value that conforms to the Error protocol. ** **
You can also use the implicitly declared constant error in the catch clause.
do {
Processing that may cause an error due to the throw statement
} catch {
Error handling
The error value can be accessed through the constant error
}
In the following sample code An error is generated by defining a SomeError type that conforms to the Error protocol.
To move to the catch clause before print () is executed The catch clause print () is executed instead of the do clause print ().
struct SomeError: Error {}
do {
throw SomeError()
print("Success")
} catch {
print("Failure: \(SomeError())")
}
Execution result
Failure: SomeError()
You can also separate the errors in the catch clause. Use ** pattern matching ** as a way to separate.
The pattern matching syntax is similar to the Switch statement, You have to cover all cases, There is also a catch clause for the role of the default keyword.
enum SomeError: Error {
case error
case warning(String)
}
do {
throw SomeError.warning("Isn't it strange?")
} catch SomeError.error {
print("Error")
} catch SomeError.warning(let reason) {
print("Warning: \(reason)")
} catch {
print(error)
}
Execution result
Warning:Isn't it strange?
The type that expresses the error of the throw statement is I explained at the beginning that it is a type that conforms to the Error protocol.
** Types are basically defined as enums. ** ** There is also a reason for that, because it is possible to comprehensively describe the errors that occur.
Also, instead of writing every possible error in the entire program in one type, ** It seems common to define a type for each type of error. ** **
The reason for this is when writing pattern matching I think it's because I need to describe all the cases.
The throws keyword is ** added to the definition of functions, initializers, and closures **.
By doing so ** Error handling by throw statement can be generated without using do-catch statement. ** **
func function name(argument) throws ->Return type{
Processing that may cause an error due to the throw statement
}
In the following sample code I am defining a function that doubles the value received in the argument and returns it.
When doubled, it may exceed the allowable range of Int type, so
The guard statement confirms it as int <= Int.max / 2
.
If the value of the argument is larger than the value of Int.max / 2 It is a flow to handle errors.
enum OperationError: Error {
case overCapacity
}
func double(value int: Int) throws -> Int {
guard int <= Int.max / 2 else {
throw OperationError.overCapacity
}
return int * 2
}
The throws keyword can also be used in the initializer.
When using with an initializer ** You can tell the caller an error that occurred during instantiation. ** **
In the following sample code If the value received as an argument is odd, an error will occur.
enum CheckValue: Error {
case valueIsOdd
}
struct Sample {
let value: Int
init(value: Int) throws {
guard value % 2 == 0 else {
throw CheckValue.valueIsOdd
}
self.value = value
}
}
This is the first keyword I heard when writing this article. The rethrows keyword defines a function or method by specifying the rethrows keyword. ** It seems that the error caused by the argument closure can be propagated to the caller of the function **.
That is, if you specify the returns keyword, ** You must take at least one closure that raises an error as an argument. ** **
In other words, it is the state of the sample code below. I actually wrote it, but it's difficult! !! (Lol)
The division () of the function gets an Int type value as the first argument and a closure as the second argument. If it is an even number, the value divided by 2 is used as the return value, and if it is an odd number, error processing is performed. (try will be explained in the continuation of this article.)
If the value is divisible,
throw SomeError.valueIsOdd
is not executed and the value of value is broken by 2.
enum SomeError: Error {
case valueIsOdd
}
func division(value: Int, closure: (_ value: Int) throws -> Void) rethrows -> Int {
//Divide the value received by the argument by 2 and execute in guard if the remainder is not 0
guard value % 2 == 0 else {
//Run closure
try closure(value)
return value
}
return value / 2
}
var value = 0
do {
//Since it may cause an error, describe it in the do clause.
try value = division(value: 11, closure: { (value) in
//Closure is executed if not divisible
throw SomeError.valueIsOdd
})
} catch {
print(error)
}
print(value)
Execution result
valueIsOdd
0
It's still in the middle of the implementation method, but it's good, so I'll stop here.
The rest is about ** try keyword **, so please have a look. -> [Swift] Perform error handling with do-catch statement (Part 2)
Also, please have a look at the article about other error handling.
-[Swift] Perform error handling with Optional \
Thank you for watching until the end.
Recommended Posts