Catch an exception for invalid user input in swift
This is still an issue in Swift 2. As noted, the best solution is to use a bridging header and catch the NSException in Objective C.
https://medium.com/swift-programming/adding-try-catch-to-swift-71ab27bcb5b8 describes a good solution, but the exact code doesn't compile in Swift 2 because try
and catch
are now reserved keywords. You'll need to change the method signature to workaround this. Here's an example:
// https://medium.com/swift-programming/adding-try-catch-to-swift-71ab27bcb5b8
@interface TryCatch : NSObject
+ (void)tryBlock:(void (^)())try catchBlock:(void (^)(NSException *))catch finallyBlock:(void (^)())finally;
@end
@implementation TryCatch
+ (void)tryBlock:(void (^)())try catchBlock:(void (^)(NSException *))catch finallyBlock:(void (^)())finally {
@try {
try ? try() : nil;
}
@catch (NSException *e) {
catch ? catch(e) : nil;
}
@finally {
finally ? finally() : nil;
}
}
@end
Is invalid user input a valid reason for throwing an exception?
Generally speaking, invalid or ill-formed input is not consider 'exceptional' and should be handled using something other than exceptions. But note that this is a guideline - there may well be situations where using exceptions to handle the problem would result in better code.
try-catch exceptions in Swift
It doesn't have exception handling, and this discussion in the developer forum discusses why it may be so:
but keep in mind that Cocoa and Cocoa Touch traditionally don't intend
for you to catch exceptions; they intend for you to not cause them to
be thrown in the first place. Ordinary errors should be handled with
optional types and inout NSError parameters; you should address any
situation that causes an assertion to fail (which seems to be the only
exception-throwing mechanism in Swift) by writing better code.
Catching NSException in Swift
Here is some code, that converts NSExceptions to Swift 2 errors.
Now you can use
do {
try ObjC.catchException {
/* calls that might throw an NSException */
}
}
catch {
print("An error ocurred: \(error)")
}
ObjC.h:
#import <Foundation/Foundation.h>
@interface ObjC : NSObject
+ (BOOL)catchException:(void(^)(void))tryBlock error:(__autoreleasing NSError **)error;
@end
ObjC.m
#import "ObjC.h"
@implementation ObjC
+ (BOOL)catchException:(void(^)(void))tryBlock error:(__autoreleasing NSError **)error {
@try {
tryBlock();
return YES;
}
@catch (NSException *exception) {
*error = [[NSError alloc] initWithDomain:exception.name code:0 userInfo:exception.userInfo];
return NO;
}
}
@end
Don't forget to add this to your "*-Bridging-Header.h":
#import "ObjC.h"
NSExpression catch invalid arguments
I think NSExpression is not the right tool for the job here. The class is part of the Cocoa predicate system and was designed to only accept well-formatted input.
I suggest you look for a proper math parser. I believe GCMathParser is a good choice. There's also DDMathParser.
If you insist on using NSExpression, you can catch the exception like this:
@try {
// the code that potentially raises an NSInvalidArgumentException
} @catch (NSException *exception) {
if ([[exception name] isEqualToString:NSInvalidArgumentException]) {
// your error handling
}
}
Be advised, however, that this is bad practice. Exceptions in Objective-C should only be used to catch unexpected runtime errors. Your example does not qualify.
What is the exception that should be thrown when user input is blank?
Tcl doesn't have a pre-defined hierarchy of exceptions. The throw command takes 2 arguments: type
is a list of words; and message
is an error message for humans.
You could do something like
proc getJobinfo {question} {
...
if {$cleanedanswer eq ""} {
throw {Value Empty} "Please provide a suitable answer."
} elseif {[string length $cleanedanswer] < 5} {
throw {Value Invalid} "Your answer is too short."
} else ...
return $cleanedanswer
}
If you want to trap that error:
try {
set answer [getJobinfo "What is the answer to this question"]
} trap {Value *} msg {
puts "Value Error: $msg"
}
throw
and try
interact via the type
words of the throw
call. We throw "Value Empty" or "Value Invalid". In the trap, we match Value
exactly, but we won't match *
exactly. In hindsight the *
should not be there.
The try
manpage is not super clear at first read:
trap pattern variableList script
This clause matches if the evaluation of body resulted in an error and the prefix of the -errorcode from the interpreter's status dictionary is equal to the pattern. The number of prefix words taken from the -errorcode is equal to the list-length of pattern, and inter-word spaces are normalized in both the -errorcode and pattern before comparison.
pattern is not a pattern in the regexp
or string match
sense: it's a list of words that is matched one-by-one with the list of words thrown in the try body.
The try
can be implemented with multiple traps to have cascading "catches":
try {
set answer [getJobinfo "What is the answer to this question"]
} trap {Value Empty} msg {
do something specific here
} trap {Value Invalid} msg {
do something specific here
} trap {Value} msg {
do something general for some other "throw {Value anything} msg"
} on error e {
this can be default catch-all for any other error
} finally {
any cleanup code goes here
}
Simplest way to throw an error/exception with a custom message in Swift?
The simplest approach is probably to define one custom enum
with just one case
that has a String
attached to it:
enum MyError: ErrorType {
case runtimeError(String)
}
Or, as of Swift 4:
enum MyError: Error {
case runtimeError(String)
}
Example usage would be something like:
func someFunction() throws {
throw MyError.runtimeError("some message")
}
do {
try someFunction()
} catch MyError.runtimeError(let errorMessage) {
print(errorMessage)
}
If you wish to use existing Error
types, the most general one would be an NSError
, and you could make a factory method to create and throw one with a custom message.
Why the swift throws exception despite try?
try?
does not catch exceptions. It catches thrown errors. Those are different things in Swift. Exceptions are at the Objective-C level and cannot be caught by Swift at all (they can't be safely caught in ObjC in most cases either, but that's a different discussion).
The solution in this case is to use JSONEncoder rather than JSONSerialization. JSONEncoder is a pure-Swift system. JSONSerialization is bridged from ObjC.
let body = try? JSONEncoder().encode([data])
See Handling Errors for more information:
Error handling in Swift resembles exception handling in other languages, with the use of the try, catch and throw keywords. Unlike exception handling in many languages—including Objective-C—error handling in Swift does not involve unwinding the call stack, a process that can be computationally expensive. As such, the performance characteristics of a throw statement are comparable to those of a return statement.
If you want to use JSONSerialization, it's important to recognize that it is a programming error to call it this way. The exception is intended to crash the program (even in ObjC). The correct way to write this code is:
if JSONSerialization.isValidJSONObject([data]), // <=== first, check it is valid
let body = try? JSONSerialization.data(withJSONObject: [data]) {
print("success")
} else {
print("unable to make body for call")
}
See the docs for more information:
If obj will not produce valid JSON, an exception is thrown. This exception is thrown prior to parsing and represents a programming error, not an internal error. You should check whether the input will produce valid JSON before calling this method by using isValidJSONObject(_:).
The thrown error from JSONSerialization is only to indicate an internal error in the serializer, not an attempt to encode an invalid object:
error
If an internal error occurs, upon return contains an NSError object with code NSPropertyListWriteInvalidError that describes the problem.
Related Topics
Change Lock Screen Background Audio Controls Text
Swift Uicolor Initializer - Compiler Error Only When Targeting Iphone5S
Migrating from Uiwebview to Wkwebview
How to Delay a Method Call for 1 Second
Iphone:How to Detect the End of Slider Drag
Query Available iOS Disk Space with Swift
Ios11 Photo Library Access Is Possible Even If Settings Are Set to "Never"
Extra Padding Above Table View Headers in iOS 15
Detect "Allow Notifications" Is On/Off for iOS8
Calculate Total Traveled Distance iOS Swift
How to Use Sfsafariviewcontroller with Swiftui
Does App Store Reject Submission If Nsallowsarbitraryloads Set to Yes
Xcode6: Run Two Instances of the Simulator
How to Remove an iOS App from the App Store
Uicollectionview Auto Scroll to Cell at Indexpath
Tap on a Part of Text of Uilabel
Difference Between Presentviewcontroller and Uinavigationcontroller