Correct way to catch NSInvalidArgumentException when using NSExpression [duplicate] - swift

This question already has answers here:
Catch an exception for invalid user input in swift
(3 answers)
Closed 5 years ago.
I want to validate user created expressions (like "2+2", "5+7" or more complex). I use NSExpression class to parse and calculate this expressions. This is my Playground code:
import UIKit
let string = "2+2"
var ex:NSExpression?
do {
ex = NSExpression(format: string)
}
catch {
print("String is not valid expression")
}
if let result = ex?.expressionValue(with: nil, context: nil) as! NSNumber? {
print("result is \(result)")
}
When I use valid expression ("2+2") - I get the result. But sometime user can provide wrong string ("2+" for example). With this string my app crashes with this:
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Unable to parse the format string "2+ == 1"'
I don't understand how I can catch this exception and why code above don't do this. Now I use Objective C class (with same logic), calling this method from my swift code, and in that class I really can catch such exception:
+(NSNumber *)solveExpression:(NSString *)string
{
id value;
#try {
NSExpression *ex = [NSExpression expressionWithFormat:string];
value = [ex expressionValueWithObject:nil context:nil];
}
#catch (NSException *e) { }
return value;
}
This works and I can get correct parse state (nil means problems with expression) and result (NSNumber), but I really want to understand how to do all this things correct and entirely in Swift.

NSInvalidArgumentException is not a catchable error in the sense of Java exceptions. Apple doesn't guarantee that your program will be in a correct state when you catch this exception and chances are that something will go wrong.
You should probably use some other mechanism to check if the string is valid before you pass it to the method.

This is what the book Using Swift with Cocoa and Objective-C has to say:
Although Swift error handling resembles exception handling in Objective-C, it is entirely separate functionality. If an Objective-C method throws an exception during runtime, Swift triggers a runtime error. There is no way to recover from Objective-C exceptions directly in Swift. Any exception handling behavior must be implemented in Objective-C code used by Swift.
[My bold]
Having just skimmed the reference for NSExpression I can't see a simple way around the issue. The quote above recommends writing a a bit of Objective-C code to do it. The simplest way is probably to create a C function:
Declaration:
extern NSExpression* _Nullable makeExpression(NSString* format _Nonnull);
Definition
NSExpression* _Nullable makeExpression(NSString* format _Nonnull)
{
NSExpression* ret = nil;
#try
{
// create and assign the expression to ret
}
#catch(NSException* e)
{
// ignore
}
return ret;
}
The function returns nil for expressions that are in error.
You could probably add an NSError** parameter to be used when there is a failure. You could also probably make this a method in a category on NSExpression and then the return nil for error/fill in NSError pattern would probably be imported to Swift as a Swift throwing method.
I should say, by the way, that an Objective-C exception is not really guaranteed to leave your program in a consistent state. Fingers crossed it is OK in this case.

Related

Crash detect swift

NSSetUncaughtExceptionHandler catches only Objective-C exceptions. I need to catch only swift exceptions. Is it possible?
NSSetUncaughtExceptionHandler catches this one. let arr = NSArray() let x = arr[4]
I also want to catch this crash. let number: Int? = nil let val = number!
As I already mentioned in my comment above
Swift does not deal with exception, it deals with error, hence when you use try catch block of swift for let x = arr[4] you cant catch the exceptions (Its array out of bound exception not an error).
As Vadian also pointed out Swift encourages you to catch and solve those exceptions rather than catching them at run time.
That being said, there might be scenarios when you have to deal with exceptions explicitly, example you are dealing with third party framework which is written in Objective-C and might throw an exception or you might be dealing with certain iOS API which still runs in Objective-C runtime and throws exception in such cases, its handy to have a have method of your own in objective C and pass swift statements to it using block / closure and execute them inside Objective-C try catch block,
How can you do it?
Add an Objective-C file to your swift project, it will ask you should it create bridging header as well, say yes, update your .h and .m files as shown
ExceptionCatcher.h
#import <Foundation/Foundation.h>
#interface ExceptionCatcher : NSObject
+ (void)catchExceptionIfAny: (void(^)(void)) executeCodeBlock withCompletion: (void (^)(NSException *)) completion;
#end
ExceptionCatcher.m
#import <Foundation/Foundation.h>
#import "ExceptionCatcher.h"
#implementation ExceptionCatcher : NSObject
+ (void)catchExceptionIfAny: (void(^)(void)) executeCodeBlock withCompletion: (void (^)(NSException *)) completion {
#try {
executeCodeBlock();
}
#catch (NSException *exception) {
NSLog(#"%#", exception.description);
completion(exception);
}
}
#end
Finally, update your bridging header file with
#import "ExceptionCatcher.h"
And come to swift file and use it as
let x = NSArray()
ExceptionCatcher.catchExceptionIfAny({[weak self] in
let a = x[4]
debugPrint(a)
}) { (exception) in
if let e = exception {
debugPrint(e.description)
}
}
As you can see obviously x[4] should end up with array out of bound exception, now instead of crashing exception will be captured and returned to your completion block.
The code above works because x is declared as NSArray which is extended form NSObject if you use x = [1,2,3] you will be creating an instance of Swift Array and obviously swift array wont throw exception. So be very careful when you use this method.
This is not a work around to catch all sort of Exceptions and should be used sparingly (only in cases when avoiding it is impossible) This is an overkill and unnecessary for this usecase however

How to convert Swift optional into Objective-C

I have read up on Swift optionals, specifically if let, but cannot make sense of this code. The tutorial is a swift project, but I am trying to use it to update an old Obj-C project. I am not trying to create an if let optional in objective-c, but rather just figure out how to make an Obj-C version do what he is doing here with Swift. The underlying code doesn't return a value.
if let user = user << Obj alternative
Following a tutorial and here is the code:
[[FIRAuth auth] signInWithEmail:userEmail
password:userPass
completion:^(FIRUser * _Nullable user, NSError * _Nullable error) {
if(error == nil) { // No ERROR so already so user already in Firebase
if let user = user{
NSDictionary *userData = #{ #"provider": user.providerID};
[[DataService instance] createFirebaseDBUserWithUid:user.uid
userData:userData
isDriver:NO];
}
DLog(#"Email user Auth successfully in Firebase");
Here is the createFirebaseDBUserWithUid:user code (possible returns a Firebase id value)
#objc func createFirebaseDBUser(uid: String, userData: Dictionary<String, Any>, isDriver: Bool) {
if isDriver {
REF_DRIVERS.child(uid).updateChildValues(userData)
} else {
REF_USERS.child(uid).updateChildValues(userData)
}
}
Would an obj-c version just be:
if( user != nil ){
NSDictionary *userData = #{ #"provider": user.providerID};
[[DataService instance] createFirebaseDBUserWithUid:user.uid userData:userData isDriver:NO];
}
The trick with Objective-C of course is its behaviour if you send any message to anil reference.
At the time of its invention (1986) it was actually progress to have any method sent to a nil reference (which is 'faulty' in an if statement since it is 0 (the number) at the same time) simply returning nil (or 0 if you interpret it as a number) and again 'faulty'.
In this sense you are 'mostly' correct if you do
if (user != nil)
in Objective-C wherever you see
if let user = user {
...
}
in Swift. Technically speaking it is not entirely the same, since the let-expression will give you a non-nullable reference to the same content shadowing the optional object for the following scope. So you do not have to do any more unwrapping of the user object in the scope of the if. Note however, that the user will again be optional after the if statement. This is adequately covered by the nil check in the Objective-C counterpart.
There may be "unusual" edge cases you could miss if the author of the Objective-C code relied on the "strange" interpretation of method passing in Objective-C. Nowadays it is considered bad practice to rely on these, but there used to be a time when we had chains of method calls that relied on said behaviour. Swift was invented partly to get rid of those sometimes unintended effects in method chains.
Objective-C Type is equivalent to Type! in Swift. for example: Objective-C NSDictionary would be NSDictionary! in Swift
Thus, Objective-C types are optionals by default and reading a Swift Type in Objective-C won't be affected by being an optional or not.
back to your question, as the intention of the code is not clear for me, generally, the code you wrote will generate a dictionary that contains one pair of (key, value) with the providerID which is not related to being optional or not.
the issue that you would face is if the User object was created in Swift, and you want to read it in Objective-C, then you have set it up for that

Understanding Swift + NSError - All paths through this function will call itself

After working with Objective-C for a long time I have just started to work with Swift as well.
I know that Swift uses the Error protocol instead of NSError and in future I will use custom Error implementations as well. However, currently I need to work with NSError in Swift and I found the following code to easily access NSError properties on Error:
extension Error {
var code: Int { return (self as NSError).code }
var userInfo: [AnyHashable? : Any?] { return (self as NSError).userInfo }
}
While I found an example for the code part in an answer here, I added the userInfo part myself. It is now problem to access someError.code using this extension. However, when using someError.userInfo the app chrashes after calling the getting over and over again.
Xcode shows a warning which explains the source of the problem:
All paths through this function will call itself
Why is this?
As far as I know Error is just a protocol which can be implemented by any class. Error it self does not have code or userInfo properties, so (self as NSError).userInfo should not callError.userInfo`, should it?
So I do not understand why this is a problem. Additionally I do not understand why this is a problem for userInfo but not for code...
Any idea what's the reason for this?

Swift Error Handling For Methods That Do Not Throw [duplicate]

This question already has answers here:
How do I catch "Index out of range" in Swift?
(6 answers)
Closed 5 years ago.
How to handle errors for methods or code that does not explicitly throw?
Wrapping it a do / catch block results in a compiler warning:
"'catch' block is unreachable because no errors are thrown in 'do' block"
Coming from C# / JAVA background this is an oddity to say the least. As a developer I should be able to safeguard and wrap any code block in do/catch block. Just because a method is not explicitly marked with "throw" does not mean that errors will not occur.
What you are asking is not possible in Swift, as Swift has no facility to handle runtime errors such as out-of-bounds, access violations or failed forced unwrapping during runtime. Your application will terminate if any of these serious programming errors will occur.
Some pointers:
Q: How to handle EXC_BAD_ACCESS? A: Not possible
Q: How to handle failed forced unwrap? A: Not possible
Q: How to handle array out of bounds? A: Not possible
Long story short: Don't short-cut error handling in Swift. Play it safe, always.
Workaround: If you absolutely must catch runtime-errors, you must use process boundaries to guard. Run another program/process and communicate using pipes, sockets, etc.
I suspect that you'd like to catch errors which is not explicitly marked with "throws".
This makes no sense.
You cannot catch other than errors which is explicitly marked with "throws".
So, this warning is valid.
For this example, if executed, fatal error: Index out of range will occur.
This is runtime error, and you cannot catch it.
For this example, you should check elements size like this, instead of doing try-catch error handling:
Faced with an exception thrown from a method which cannot throw. Found out this exception was thrown from objective-c part of API. So you should catch it in old style way using objective-c.
Firstly create objective-c class which takes several blocks in init method - for try, catch and finally.
#import <Foundation/Foundation.h>
/**
Simple class for catching Objective-c-style exceptions
*/
#interface ObjcTry : NSObject
/**
* Initializeer
*
* #param tryBlock
* #param catchBlock
* #param finallyBlock
*
* #return object
*/
- (_Nonnull id)initWithTry:(nonnull void(^)(void))tryBlock catch:(nonnull void(^)( NSException * _Nonnull exception))catchBlock finally:(nullable void(^)(void))finallyBlock;
#end
In .m file:
#import "ObjcTry.h"
#implementation ObjcTry
- (_Nonnull id)initWithTry:(nonnull void(^)(void))tryBlock catch:(nonnull void(^)( NSException * _Nonnull exception))catchBlock finally:(nullable void(^)(void))finallyBlock
{
self = [super init];
if (self) {
#try {
tryBlock ? tryBlock() : nil;
}
#catch (NSException *exception) {
catchBlock ? catchBlock(exception) : nil;
}
#finally {
finallyBlock ? finallyBlock() : nil;
}
}
return self;
}
#end
Second, add its headers to the Bridging Header file.
#import "ObjcTry.h"
And use it in your swift code like that:
var list: [MyModel]!
_ = ObjcTry(withTry: {
// this method throws but not marked so, you cannot even catch this kind of exception using swift method.
if let items = NSKeyedUnarchiver.unarchiveObject(with: data) as? [MyModel] {
list = items
}
}, catch: { (exception: NSException) in
print("Could not deserialize models.")
}, finally: nil)
There is a difference between ERRORS and EXCEPTIONS. Swift deals only with errors which are explicitly THROWN and has no native capability for dealing with EXCEPTIONS. As others have commented ERRORS must be thrown and you can't catch what isn't thrown.
By contrast Objective-C #try-#catch deals with exceptions, not errors,. Some objc methods may cause exceptions but do not declare them in any way to the compiler. e.g. FileHandle.write. Such exceptions are more closely aligned to Java's RuntimeException which also does not need to be declared.
There are some situations such as file handling where it would be nice to handle exceptions cleanly in Swift and it is possible by using an Objective-C wrapper. See http://stackoverflow.com/questions/34956002/how-to-properly-handle-nsfilehandle-exceptions-in-swift-2-0
Code reproduced here:
#ifndef ExceptionCatcher_h
#define ExceptionCatcher_h
#import <Foundation/Foundation.h>
NS_INLINE NSException * _Nullable tryBlock(void(^_Nonnull tryBlock)(void)) {
#try {
tryBlock();
}
#catch (NSException *exception) {
return exception;
}
return nil;
}
#endif /* ExceptionCatcher_h */
Then calling it from Swift:
let exception = tryBlock {
// execute dangerous code, e.g. write to a file handle
filehandle.write(data)
}
if exception != nil {
// deal with exception which is of type NSException
}
As others mentioned, you should not catch these errors, you should fix them, but in case you want to execute more code before the program terminates, use NSSetUncaughtExceptionHandler in AppDelegate in applicationdidFinishLaunchingWithOptions function.
The function description:
Changes the top-level error handler.
Sets the top-level error-handling
function where you can perform last-minute logging before the program
terminates.
You simply cannot. The whole do-try-catch or do-catch statement is meant to be used to catch unhandled errors and...
I mean there is no point in catching error if no error occurs in first place... I see no scenario why would you want to do such thing, you make only compiler angry for no reason.
It's the same scenario if you safely unwrap optional with if let or guard let statements
guard let smth = smthOpt?.moreSpecific else { return }
//Compiler gives warning - unused variable smth. You wouldn't declare the variable and then not use it, or you would?
Simply Do-Catch is not meant to be used for safe use and I don't see any reason why to use it when not dealing with risky operations which need to catch...
for further understanding, see:
https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/ErrorHandling.html

removePersistentStore(_:) doesn't return Bool in Swift

I'm trying to store the return value of removePersistentStore(_:) which is clearly a Bool according to the documentation:
I'm using it like that:
let removed = try! storeCoordinator.removePersistentStore(store)
But get an alert that removed is just Void:
What's the deal with this? Am I missing something?
Return Value
YES if the store is removed, otherwise NO.
in the NSPersistentStoreCoordinator documentation refers to the Objective-C signature of
the method:
- (BOOL)removePersistentStore:(NSPersistentStore *)store
error:(NSError * _Nullable *)error
In Swift, the method throws an error instead of returning a boolean:
func removePersistentStore(_ store: NSPersistentStore) throws
so that you can call it as
try! storeCoordinator.removePersistentStore(persistentStore)
or better:
do {
try storeCoordinator.removePersistentStore(persistentStore)
} catch let error as NSError {
print("failed", error.localizedDescription)
}
Compare Error Handling in the "Using Swift with Cocoa and Objective-C" reference:
If the last non-block parameter of an Objective-C method is of type NSError **, Swift replaces it with the throws keyword, to indicate that the method can throw an error.
...
If an error producing Objective-C method returns a BOOL value to indicate the success or failure of a method call, Swift changes the return type of the function to Void.