Providing a unified Swift error API
For an up-to-date version of this article — read it on Swift by Sundell instead, where you can also find over 100 articles about Swift. This Medium version has not been updated for the latest version of Swift, or with other improvements, such as syntax highlighting.
I want to share a technique that I’ve come to find quite useful when using the Swift do, try, catch error handling model, to limit the amount of errors that can be thrown from a given API call.
At the moment, Swift does not provide typed errors (what is known as “checked exceptions” in other languages, like Java), which means that any function that throws, can potentially throw any error. While this gives us a lot of flexibility, it can also be a bit of a challenge when it comes to using the API, both for production code and in testing.
Consider the following function, which performs a search by loading data synchronously from a URL:
As you can see above, our function can throw in two different places — when attempting to construct a URL and when initializing Data with the URL. So, here’s the problem; as an API user, it becomes very unclear what kind of errors I can expect this function to throw. Not ony do I need to be aware that this function uses the Data type internally, but I also need to know what errors that Data’s initializer can throw.
Having to be aware of imlpementation details is usually a bad sign when it comes to API design, so wouldn’t it be better if we could guarantee that our function only throws Errors of the SearchError type? Luckily, it’s easily fixed. All we have to do is wrap the call to Data in a do, try, catch block. Like this:
What we do above, is to silence the error thrown by Data, and replace it with our own error instead. Now, we can document that our function always throws a SearchError, and our API becomes a lot easier to use when it comes to error handling.
However, in making our API better, we’ve also cluttered up our implementation a bit. Often you’ll need to wrap multiple calls with do, try, catch blocks, which will make our code quickly become harder to read. To solve this problem, I’ve made a simple function that does this wrapping, and throws a specific error in case an underlying error was thrown. It looks like this:
What perform does, is to execute a throwing expression and throw a custom error in case it failed. Using it, we can now update our search function from before, to make it a lot simpler:
We now have both a unified error API, and a simpler implementation! 🎉
Feel free to use the perform function in your projects — I put it up on GitHub as a Gist here.
If you have questions, feedback or comments — feel free to either post a reply here or contact me on Twitter @johnsundell.
Thanks for reading 🚀