Comment on page
Errors
There are times when, for some reason, you will not be able to finish some process. No matter the reason, could be a violation of preconditions or some error with the state, in these cases, it would be ideal to not crash the app, but provide some helpful errors to explain what went (or is going) wrong.
There are no built in errors in Swift. You can use
assert(_: Bool)
or fatalError(_:)
, but this doesn't really count. We can make any type an Error
by adopting the Error
protocol–no extra work needed! Generally speaking, we want to list all of the potential errors, which means you should usually be working with enums. If you need more information and don't want to use enums with associated values, you can store an enum as a member inside a larger object/value. But generally speaking, enums are the best practice.We can imagine when working with networking, that the following issues may arise:
enum NetworkError {
case invalidURL(String)
case serverError
case invalidData
case poorConnection
}
Note: you'd normally use the
URL
type, but this is only for demonstration purposes, so do not use this XDNow that we have built an error, raising them is super easy! Just create an instance, and write
throw
<error>. With the example above, we could do:throw NetworkError.serverError
As with everything in Swift, we need to be explicit about what is going on. Thus, the potential for errors becomes part of types. If a function can raise an error, we say that it
throws
. We write the throws
right after the parameters of a function, but before the ->
for return type if there is one.func network() throws -> Data {
...
}
If a process is marked as throwing, you will need to acknowledge the error in how you handle it. We have three methods to deal with errors, but they can be slightly confusing because they all use the
try
keyword.This is probably pretty similar to how you have handled errors in your other languages. We use a do-catch block. Every time there is a potential error in a do block we must use some try.
do {
let someData = try network()
...
let someMoreData = try network()
...
}
...
The catch, where you actually handle the error, is very flexible. We can categorize instructions by the type of error. You can think of this almost like a switch block, except you don't need to handle all of the potential cases (because there could be so many types of errors).
We can catch a specific enum case just by using
catch
followed by the case:... } catch NetworkError.serverError {}
You can also use a let-statement to bind an associated value.
... } catch NetworkError.invalidURL(let url) {}
If you do not care about the specific information in an error, just about its type then you can use the
is
keyword:... } catch is NetworkError { ... }
If you want a certain block to execute given a type, but you actually need further differentiation (and could not get that with the above methods) you can do a conditional assignment. I think of this as a
where
clause.... } catch let e as NetworkError {
switch e { ... }
}
I'm just using switch as an example–you can do whatever you want.
You may gravitate towards this option if you have chosen to use a class or struct for your error.
In the case where you don't catch an error, it will automatically be raised (i.e. you didn't handle it, so it will run free and stop your program). As with switches, there is a default case. Just write catch. This will assign the error to the identifier
error
:... catch {
// error: Error = ...
}
Catching can be a lot. What if you only want to do something if no error is thrown and just ignore any other case? As the name implies,
try?
convert our results into optionals! If there is no error, it returns an optional and otherwise a nil
value. In this case, we don't need a do-catch block.let myData: Data? = try? network()
As you may have guessed, this is the force-unwrapped version of errors. We are guaranteeing that whatever we are doing, even though it is marked as
throws
, will never raise an error. We are so convinced of this fact, that if we are wrong, we are okay with the program crashing. You do not need a do-catch block.let myData: Data = try! network()
Last modified 1yr ago