I have a question about error/exception handling on the iPhone.
I've read the documentation about Exception, and it seems exceptions can be used only for exceptional situations.
Does it mean you can't use them like in java ?
for example, I'm trying to write a use case controller for my application. I've got a few examples in Java from previous projects in that language, that use exceptions in case of errors.
the question put simply is: can I follow the example I've got in Java, and "translate" it in Objective-C (and use Objective-C exceptions) or is there a better way to do that ?
here is the code I would like to make objective-c friendly:
public void addPerformance(Perfomance perf) {
//do some preparation
...
//execute the usecase
executor(new AddPerformance(perf));
}
private void executor(Usecase usecase) {
try {
UnitOfWorkServices.INSTANCE.bizTransactionStart();
usecase.execute();
UnitOfWorkServices.INSTANCE.bizTransactionCommit();
} catch (RealException re) {
UnitOfWorkServices.INSTANCE.bizTransactionEscape();
throw re;
} catch (Exception e) {
UnitOfWorkServices.INSTANCE.bizTransactionEscape();
throw new FatalException(this.getClass().getName() + " / executor("
+ usecase.getClass().getSimpleName() + ")", e,
"APPXCP_006_UNEXPECTED_EXCEPTION",
"\n\t |*| : Unexpected exception translated into FatalException");
} finally {
UnitOfWorkServices.INSTANCE.bizTransactionEnd();
}
}
All the exceptions are meant to be caught by the UI to display an error message.
thanks for your help,
Michael
In general, yes, you can translate your try/catch logic into a comparable construct in Objective-C and get comparable results. Though do be careful with that throw re; line, as an uncaught exception on the iPhone will crash your app.
More to the point however, the standard pattern used by the iPhone SDK and other commonly used libraries is that instead of throwing exceptions, API methods that may fail for a variety of reasons return a boolean value indicating whether or not the operation was successful, and accept as a parameter a reference to an NSError pointer that is used in the event of an error to provide the caller with specific details about what went wrong. So in this case, your code might translate to something more like:
NSError* error = nil;
[[UnitOfWorkServices sharedInstance] bizTransactionStart];
bool success = [usecase execute:&error];
if (success) {
[[UnitOfWorkServices sharedInstance] bizTransactionCommit];
}
else if (error) {
int code = [error code];
[[UnitOfWorkServices sharedInstance] bizTransactionEscape];
if (code == MY_MINOR_ERROR_CODE) {
//do something
}
else if (code == MY_FATAL_ERROR_CODE) {
//do something else
}
else {
//handle unexpected error type(s)
}
}
else {
//operation failed with no specific error details
[[UnitOfWorkServices sharedInstance] bizTransactionEscape];
//handle generic error
}
[[UnitOfWorkServices sharedInstance] bizTransactionEnd];
You can use exceptions just as you would use exceptions in Java, but it is not the recomended way to do it. Translating Java to Objective-C that way works, and most people will understand your intentions. But the result will be just as bad as if you tried to translate English to German using a dictionary; the spelling is perfect, but the grammar and context is completely wrong.
Understanding the "Cocoa way" to handle errors and exceptions will help you create more robust iOS applications, and give you a better understanding of how to use the official and third party API:s.
I have written a longer blog post on the topic here: http://blog.jayway.com/2010/10/13/exceptions-and-errors-on-ios/
Lets recap the important parts:
Exceptions should only be used for programming errors, and fatal stuff that you can not recover from. For example out of bounds exception, if you called with the wrong argument the first time your app is in error, retrying 100 times will not fix your app, only keep failing. The exceptions are exceptions to normal workflow and there to help you fix all bugs before you ship the app to end users.
NSError instances is what you use for errors caused by the users, and stuff that your application can recover from. For example IO-errors, the user might want to re-connect to the network, try a new password, and try again. This is for errors that you can predict, and that the app or the user can do something about.
Related
Dart explicitly makes a distinction between Error, that signals a problem in your code's logic and should never happen and should never be caught and Exceptions that signal a problem based on run-time data.
I really like this distinction but I wonder when should I then use assert() functions?
Asserts are ways to perform code useful in development only, without hindering the performances of release mode – usually to prevent bad states caused by a missing feature in the type system.
For example, only asserts can be used to do defensive programming and offer a const constructor.
We can do:
class Foo {
const Foo(): assert(false);
}
but can't do:
class Foo {
const Foo() { throw 42; }
}
Similarly, some sanity checks are relatively expensive.
In the context of Flutter, for example, you may want to traverse the widget tree to check something on the ancestors of a widget. But that's costly, for something only useful to a developer.
Doing that check inside an assert allows both performance in release, and utility in development.
assert(someVeryExpensiveCheck());
Background
In Dart an Exception is for an expected bad state that may happen at runtime. Because these exceptions are expected, you should catch them and handle them appropriately.
An Error, on the other hand, is for developers who are using your code. You throw an Error to let them know that they are using your code wrong. As the developer using an API, you shouldn't catch errors. You should let them crash your app. Let the crash be a message to you that you need to go find out what you're doing wrong.
An assert is similar to an Error in that it is for reporting bad states that should never happen. The difference is that asserts are only checked in debug mode. They are completely ignored in production mode.
Read more on the difference between Exception and Error here.
Next, here are a few examples to see how each is used in the Flutter source code.
Example of throwing an Exception
This comes from platform_channel.dart in the Flutter repo:
#optionalTypeArgs
Future<T?> _invokeMethod<T>(String method, { required bool missingOk, dynamic arguments }) async {
assert(method != null);
final ByteData? result = await binaryMessenger.send(
name,
codec.encodeMethodCall(MethodCall(method, arguments)),
);
if (result == null) {
if (missingOk) {
return null;
}
throw MissingPluginException('No implementation found for method $method on channel $name');
}
return codec.decodeEnvelope(result) as T;
}
The MissingPluginException here is a planned bad state that might occur. If it happens, users of the platform channel API need to be ready to handle that.
Example of throwing an Error
This comes from artifacts.dart in the flutter_tools repo.
TargetPlatform _currentHostPlatform(Platform platform) {
if (platform.isMacOS) {
return TargetPlatform.darwin_x64;
}
if (platform.isLinux) {
return TargetPlatform.linux_x64;
}
if (platform.isWindows) {
return TargetPlatform.windows_x64;
}
throw UnimplementedError('Host OS not supported.');
}
First every possibility is exhausted and then the error is thrown. This should be theoretically impossible. But if it is thrown, then it is either a sign to the API user that you're using it wrong, or a sign to the API maintainer that they need to handle another case.
Example of using asserts
This comes from overlay.dart in the Flutter repo:
OverlayEntry({
required this.builder,
bool opaque = false,
bool maintainState = false,
}) : assert(builder != null),
assert(opaque != null),
assert(maintainState != null),
_opaque = opaque,
_maintainState = maintainState;
The pattern in the Flutter source code is to use asserts liberally in the initializer list in constructors. They are far more common than Errors.
Summary
As I read the Flutter source code, use asserts as preliminary checks in the constructor initializer list and throw errors as a last resort check in the body of methods. Of course this isn't a hard and fast rule as far as I can see, but it seems to fit the pattern I've seen so far.
As asserts are ignored in production mode, you should use them as way to do initial tests to your code logic in debug mode:
In production code, assertions are ignored, and the arguments to assert aren’t evaluated.
When exactly do assertions work? That depends on the tools and framework you’re using:
Flutter enables assertions in debug mode.
Development-only tools such as dartdevc typically enable assertions by default.
Some tools, such as dart and dart2js, support assertions through a command-line flag: --enable-asserts.
In production code, assertions are ignored, and the arguments to assert aren’t evaluated.
Refer:https://dart.dev/guides/language/language-tour#assert
Im used to typical try catch blocks to handle exceptions of any function or block of code like objective C.
However, after making leap of faith to Swift and reading about Swift’s error handling; it’s not much what I expected. You must handle your exceptions by throwing errors and guard and checking if else everything.
I need general Swift error handling tips and best practices, and especially for the following cases:
Im using Alamofire for calling services, which has closure, and calling it through another function with closure too. AFAIK I can’t throw error inside async code, so what is best practice for such case? will try to update with code sample
Is it favourable to have every function in the app throw errors? Just in case ? Like checking every single value or result.
Can I have a Singleton Error handling module?
Thanks in advance
You must handle your exceptions by throwing errors and guard and checking if else everything.
This is an assumption that doesn't have to be true. If your code is structured correctly, you shouldn't have to check everything with if lets and throws.
I need general Swift error handling tips and best practices ...
Before you look at anything else, read the following pages in order. They should give you a good background on best practices.
Error Protocol Apple Developer Documentation
Error Handling - Apple's Swift Programming Language Guide
Magical Error Handling in Swift - Ray Wenderlich
Im using Alamofire for calling services, which has closure, and calling it through another function with closure too. AFAIK I can’t throw error inside async code, so what is best practice for such case?
You can. A good practice is to pass a closure as a parameter to your service-calling function, then call the closure when the asynchronous operation is complete, like so:
functionThatCallsAService(completion: #escaping (Data?, NetworkingErrors?) -> ()) {
session.dataTask(with: request) { data, response, error in
guard error == nil else {
completion(nil, NetworkingErrors.returnedError(error!))
return
}
completion(data, nil)
}.resume()
}
enum NetworkingErrors: Error {
case errorParsingJSON
case noInternetConnection
case dataReturnedNil
case returnedError(Error)
case invalidStatusCode(Int)
case customError(String)
}
Is it favourable to have every function in the app throw errors? Just in case? Like checking every single value or result.
If you know for sure that a function or value won't be nil/cause a runtime error/throw an error, then don't check for it! But generally, according to the web pages above, you should check for nil and/or errors from calling a web service, interacting with the file system, creating a complex object, etc.
Can I have a Singleton Error handling module?
You technically can, but I don't see any reason to do so besides logging.
enum CheckValidAge : Error{
case overrage
case underage
}
func checkValidAgeForGovernmentJob(age:Int)throws -> Bool{
if age < 18{
throw CheckValidAge.underage
}else if age > 25{
throw CheckValidAge.overrage
}else{
return true
}
}
do {
try checkValidAgeForGovernmentJob(age: 17)
print("You are valid for government job ")
}catch CheckValidAge.underage{
print("You are underage for government job ")
}catch CheckValidAge.overrage{
print("You are overrage for government job ")
}
try checkValidAgeForGovernmentJob(age: 17) OutPut : You are underage for government job
try checkValidAgeForGovernmentJob(age: 26) OutPut : You are overrage for government job
try checkValidAgeForGovernmentJob(age: 18) OutPut : You are valid for government job
I would like to throw an exception from some "deep" function, so it bubbles up to another function, where I want to catch it.
f1 calls f2 calls f3 calls ... fN which may throw an error
I would like to catch the error from f1.
I've read that in Swift I have to declare all methods with throws, and also call them using try.
But that's quite annoying:
enum MyErrorType : ErrorType {
case SomeError
}
func f1() {
do {
try f2()
} catch {
print("recovered")
}
}
func f2() throws {
try f3()
}
func f3() throws {
try f4()
}
...
func fN() throws {
if (someCondition) {
throw MyErrorType.SomeError
}
}
Isn't there a similar concept to the RuntimeException in Java, where throws doesn't leak all the way up the call chain?
Yes, it is possible!
Use: fatalError("your message here") to throw runtime exception
To elaborate on Максим Мартынов's answer, Swift has 3 ways to do throw undeclared, uncatchable errors (but other approaches are possible if you want to venture outside Swift's standard library). These are based on the 3 levels of optimization:
-Onone: No optimization; debug build
-O: Normal optimization; release build
-O SWIFT_DISABLE_SAFETY_CHECKS: Unchecked optimization; extremely optimized build
1. assertionFailure(_:)
Write this line when you're doing debugging tests and hit a line you don't think should ever be hit. These are removed in non-debug builds, so you must assume they will never be hit in the production app.
This has a sister function called assert(_:_:), which lets you assert at runtime whether a condition is true. assertionFailure(_:) is what you write when you know the situation is always bad, but don't think that'll harm the production code very much.
Usage:
if color.red > 0 {
assertionFailure("The UI should have guaranteed the red level stays at 0")
color = NSColor(red: 0, green: color.green, blue: color.blue)
}
2. preconditionFailure(_:)
Write this line when you're sure some condition you've described (in documentation, etc.) was not met. This works like assertionFailure(_:), but in release builds as well as debug ones.
Like assertionFailure(_:), this one's got a sister function called precondition(_:_:), which lets you decide at runtime whether a precondition was met. preconditionFailure(_:) is essentially that, but assuming the precondition is never met once the program gets to that line.
Usage:
guard index >= 0 else {
preconditionFailure("You passed a negative number as an array index")
return nil
}
Note that, in extremely optimized builds, it is not defined what happens if this line is hit! So if you don't want your app to wig out if it might ever hit this, then make sure the error state is handleable.
3. fatalError(_:)
Used as a last resort. When every other attempt to save the day has failed, here is your nuke. After printing the message you pass to it (along with the file and line number), the program stops dead in its tracks.
Once the program gets to this line, this line always runs, and the program never continues. This is true even in extremely optimized builds.
Usage:
#if arch(arm) || arch(arm64)
fatalError("This app cannot run on this processor")
#endif
Further reading: Swift Assertions by Andy Bargh
The error handling mechanism in Swift does not involve raising unchecked (runtime) exceptions. Instead, explicit error handling is required. Swift is certainly not the only recently designed language to go for this design – for instance Rust and Go also in their own ways also require explicitly describing the error paths in your code. In Objective-C the unchecked exception feature exists, but is largely used only for communicating programmer errors, with the notable exception of a few key Cocoa classes such as NSFileHandle which tends to catch people out.
Technically you do have the ability to raise Objective-C exceptions in Swift with the use of NSException(name: "SomeName", reason: "SomeReason", userInfo: nil).raise() as is explained in this excellent answer to this question, arguably a duplicate of your question. You really shouldn't raise NSExceptions though (not least because you have no Objective-C exception catching language feature available to you in Swift).
Why did they go with this design? Apple's "Error Handling in Swift 2.0" document explains the rationale clearly. Quoting from there:
This approach […] is very similar to the error handling model manually
implemented in Objective-C with the NSError convention. Notably, the
approach preserves these advantages of this convention:
Whether a method produces an error (or not) is an explicit part of its API contract.
Methods default to not producing errors unless they are explicitly marked.
The control flow within a function is still mostly explicit: a maintainer can tell exactly which statements can produce an error, and
a simple inspection reveals how the function reacts to the error.
Throwing an error provides similar performance to allocating an error and returning it – it isn’t an expensive, table-based stack
unwinding process. Cocoa APIs using standard NSError patterns can be
imported into this world automatically. Other common patterns (e.g.
CFError, errno) can be added to the model in future versions of Swift.
[…]
As to basic syntax, we decided to stick with the familiar language of
exception handling. […] by and large, error propagation in this
proposal works like it does in exception handling, and people are
inevitably going to make the connection.
Isn't there a similar concept to the RuntimeException in Java, where throws doesn't leak all the way up the call chain?
Swift does indeed have error handling which doesn't propegate at compile time.
However, before I discuss those, I must say that the one you point out, where you use the language's do...catch, try, throw, and throws keywords/features to handle errors, is by far the safest and most preferred. This ensures that every single time an error might be thrown or caught, it's handled correctly. This completely eliminates surprise errors, making all code more safe and predictable. Because of that inherent compile- and run-time safety, you should use this wherever you can.
func loadPreferences() throws -> Data {
return try Data(contentsOf: preferencesResourceUrl, options: [.mappedIfSafe, .uncached])
}
func start() {
do {
self.preferences = try loadPreferences()
}
catch {
print("Failed to load preferences", error)
assertionFailure()
}
}
guard let fileSizeInBytes = try? FileManager.default.attributesOfItem(atPath: path)[.size] as? Int64 else {
assertionFailure("Couldn't get file size")
return false
}
Probably the easiest way to silence Swift's compiler is with try! - this will allow you to use native Swift errors, but also ignore them.
Here's what your example code would look like with that:
enum MyErrorType : ErrorType {
case SomeError
}
func f1() {
f2()
}
func f2() {
f3()
}
func f3() {
try! f4()
}
...
func fN() throws {
if (someCondition) {
throw MyErrorType.SomeError
}
}
Obviously, this has the problem of not allowing you to ever catch these, so if you want a silent error you can catch, read on.
There are also assertions, preconditions, and fatalErrors, which I described in detail in my answer from October of 2017. The compiler provides reasonable handling of these, such as ensuring that return statements and other control flow are placed and omitted when appropriate. Like try!, however, these cannot be caught.
exit is in this family if your goal is to stop the program immediately.
If you venture outside Swift into the wider Apple ecosystem (that is, if you are writing Swift on an Apple platform), you also see Objective-C's NSException. As you desire, this can be thrown by Swift without using any language features guarding against that. Make sure you document that! However, this cannot be caught by Swift alone! You can write a thin Objective-C wrapper that lets you interact with it in the Swift world.
func silentButDeadly() {
// ... some operations ...
guard !shouldThrow else {
NSException.raise(NSExceptionName("Deadly and silent", format: "Could not handle %#", arguments: withVaList([problematicValue], {$0}))
return
}
// ... some operations ...
}
func devilMayCare() {
// ... some operations ...
silentButDeadly()
// ... some operations ...
}
func moreCautious() {
do {
try ObjC.catchException {
devilMayCare()
}
}
catch {
print("An NSException was thrown:", error)
assertionFailure()
}
}
Of course, if you're writing Swift in a Unix environment, you still have access to the terrifying world of Unix interrupts. You can use Grand Central Dispatch to both throw and catch these. And, as you desire, there's no way for the compiler to guard against them being thrown.
import Dispatch // or Foundation
signal(SIGINT, SIG_IGN) // // Make sure the signal does not terminate the application.
let sigintSource = DispatchSource.makeSignalSource(signal: SIGINT, queue: .main)
sigintSource.setEventHandler {
print("Got SIGINT")
// ...
exit(0)
}
sigintSource.resume()
exit is in this family if your goal is to trap it and read its code.
I have come across functions that return Future but also throw exceptions immediately. For example like this:
def func(): Future[String] {
if (something) {
// this
Future.failed(new RuntimeException("test"))
} else {
// and this
throw new RuntimeException("test")
}
}
This behaviour seems annoying for the caller, because you have to do something like this to catch both errors:
try {
func() recover {
case e: Exception => handleError(e)
}
} catch {
case e: Exception => Future.successful(handleError(e)) //or Future.failed etc
}
I have noticed that the WSClient in play framework does this (both throwing exceptions if the URL is malformed, and returning a Future which fails if the HTTP request fails).
Is this good practice? Is there a better way to handle errors from functions that behave like this?
Future is used to return something eventually, but its not clear when this actually happens.
Coming from a .NET perspective (we have Task): an exception should be thrown if the request is clearly invalid (such as an malformed url). You already know this before actually making the web request, so there is no need in delaying the exceptions.
On the other hand, if the server is down (timeout?) or the server returns something your client doesn't understand: this can and must be handled at a later time, since the response is not directly available when making the call. We would be able to block until the response is available, but that would render the Future useless.
I think this is best compared with the 'Exit early' programming style.
I have some (ugly) self-written code ported to Swift2 and got this error message in a lambda function:
What I didn't understand is, that I handle the whole code with the error throwing function JSONObjectWithData and catch the error. I throw nothing in the code. Nevertheless the compiler means that I am throwing an error.
I need to understand this behavior. Please be kind because I know that I have to improve my code to make full use of the new error handling concept in swift2.
Thank you very much in advance.
This was fast. I have figured the solution for my problem out with a little help of this article:
http://www.hackingwithswift.com/new-syntax-swift-2-error-handling-try-catch
you have to put a general catch clause at the end of the code because the catch of NSError alone is not sufficient.
catch let error as NSError
{
failure(error: error)
return
}
// this is important -->
catch
{
}
I think the best way forward is to change your failure function signature to take an ErrorType. Then just
catch let error {
failure(error: error)
}
will do.