Crash detect swift - 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

Related

Objective-C interface Named "Category" cannot be imported into swift via the bridging header

The below swift code throws this error ('Category' is ambiguous for type lookup in this context)
// ctx is the UIContext, fetchCategories is an Obj-C function that queries CoreData
//for the Categories, the cast shouldn't even be necessary but swift will not
//recognize my obj-c interface
if let cats = self.fetchCategories(ctx) {
let array = cats as! [Category] //error happens on this line
return array
}
This is the implementation in my bridging header,
there are many other imports but I removed them for this post, so note that it is a fully functional bridging header for almost 40 other .h files
#ifndef AppDataRoom_Bridging_Header_h
#define AppDataRoom_Bridging_Header_h
#import "Category.h"
#endif /* AppDataRoom_Bridging_Header_h */
Below is the interface implementation
#interface Category : _Category <QLPreviewItem> {
UIImage *squareThumb;
}
I realize that changing the name of the Category class would be the best option since this is probably due to the naming convention being common, However when I change the code to this :
if let cats = self.fetchCategories(ctx) {
let array = cats as! [<ModuleName>.Category] //error happens on this line
return array
}
Is still says it is too ambiguous to lookup. Any ideas on other things I could try besides changing the interface name, because that really isn't an option and I will spare you all the reasons why.

Correct way to catch NSInvalidArgumentException when using NSExpression [duplicate]

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.

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

"Ambiguous use of 'children'" when trying to use NSTreeController.arrangedObjects in Swift 3.0

I get an Ambiguous use of 'children' error in XCode 8.0/Swift 3.0 when trying to send a message to the opaque NSTreeController.arrangedObjects object.
Here is a bare playground showing the use case :
import AppKit
extension NSTreeController {
func whatever () {
let k = (self.arrangedObjects as AnyObject).children // error here
}
}
I try to use AnyObject as a bridge to the underlying ObjC object, which is supposed to be able to get through any method call, I guess.
Xcode signals that it found two candidates that could respond to a "children" message: Foundation.XMLNode and AppKit.NSTreeNode.
Of course the obvious solution (casting to NSTreeNode) is not working because arrangedObjects returns an opaque, proxy object not a real NSTreeNode
Any suggestion on how we're supposed to use NSTreeController.arrangedObjects.children in Swift 3 ?
The two candidates for the children property differ by their type:
Foundation.XMLNode:137:14: note: found this candidate
open var children: [XMLNode]? { get }
^
AppKit.NSTreeNode:12:14: note: found this candidate
open var children: [NSTreeNode]? { get }
^
You can resolve the ambiguity by casting the value of the property
to the expected type. In your case:
let k = (self.arrangedObjects as AnyObject).children as [NSTreeNode]?
Another solution: Adding an Obj-C category to NSTreeController
.h
#import <Cocoa/Cocoa.h>
#interface NSTreeController (RootNodes_m)
- (NSArray *) rootNodes;
#end
.m
#import "NSTreeController+RootNodes_m.h"
#implementation NSTreeController (RootNodes_m)
- (NSArray *) rootNodes {
NSObject * arranged = self.arrangedObjects;
if ([arranged respondsToSelector: #selector(childNodes)]) {
return [arranged performSelector:#selector(childNodes)];
}
return nil;
}
#end
Now in your code you can use it like this:
return treeController.rootNodes() as? [NSTreeNode]
I had problem with the above answer: The compiler refused to compile when "whole module optimization" was turned on. A swift extension didn't help. I'm using Xcode 8.2.1.

initWithDictionary: in Objective-C and Swift

In Objective-C we can use object mapping with json response like this way
PolicyData *policyData = [[PolicyData alloc] initWithDictionary:responseObject error:&err];
Here we can map responseObject with PolicyData class properties.
How i can do the same in Swift?
It should be as easy as adding a bridging header (because PolicyData is likely written in Objective-C). Instructions on how to do this can be seen in this Apple documentation.
Then you can create that PolicyData object as easily as doing:
do {
let newPolicyDataObject = try PolicyData(responseObject)
} catch error as NSError {
print("error from PolicyData object - \(error.localizedDescription)")
}
This assumes your responseObject is a NSDictionary. And Swift 2 helpfully (?) turns error parameters into try/catch blocks.
That is, PolicyData's
- (instancetype) initWithDictionary:(NSDictionary *)responseObject error:(NSError *)err;
declaration magically becomes
func initWithDictionary(responseObject : NSDictionary) throws
as described in the "Error Handling" section of this Apple Objective-C/Swift interoperability doc.
You can add a
convenience init?(dictionary: NSDictionary)
to any object you want to initialize from a dictionary and initialize it's properties there.
Yet, as swift does no dynamic dispatching (sooner or later), you may not generalize that to expect the properties' names to be keys in the dictionary for any object.