NSAssert not work in uiwebview delegate method - ios

In a UIWebView's delegate method webView:shouldStartLoadWithRequest:navigationType:, I put an NSAssert there, but it just output a log, instead of terminating. Here is my code:
- (BOOL)webView:(UIWebView *)webView
shouldStartLoadWithRequest:(NSURLRequest *)request
navigationType:(UIWebViewNavigationType)navigationType
{
NSAssert(NO,#"assertion in delegate");
return YES;
}
and the output:
*** WebKit discarded an uncaught exception in the webView:decidePolicyForNavigationAction:request:frame:decisionListener: delegate: assertion in delegate

NSAssert raises an Objective-C exception and these can be caught, so it doesn't guarantee your program will be aborted. Using it in your own code is generally fine, but if your code is called by a framework - such as when a delegate is invoked - it depends on what the framework does. As you have discover WebKit catches exceptions and discards or handles them itself.
The simple solution is to use the standard assert() function. This takes a single Boolean expression and will abort the program printing out the expression, file name and line number of the assertion. This function does not use Objective-C exceptions, it uses the standard abort() function, and so cannot be caught.
HTH

Failed NSAsserts raise ObjC exceptions. (NSInternalInconcistencyException to be precise.) Anyone can install exception handlers or other mechanisms to define what happens to exceptions that are raised in code that they call. And those mechanisms don't have to include halting the process (though continuing after an exception is generally not a great idea).
When you raise ObjC exceptions in a callback, you're not guaranteed that execution will terminate as a result — you're at the mercy of whatever exception handling was set up by the code that called you. If you want to bring the whole process crashing down due to some failure in delegate code, it's probably best to abort() it yourself.

Related

iOS: Setting up a Mach exception handler without interfering with LLDB

I'm using the library called CwlPreconditionTesting which is used to test Swift assertions. It uses Mach exceptions handler API to catch exceptions that's available on iOS and OS X.
The library works well in simulators, but devices aren't supported by it. The reason for this is that on devices, the Swift assert functions (e.g. fatalError) crash with EXC_BREAKPOINT exception type, which is also the exception type the debugger uses when someone puts a breakpoint somewhere and the debugger wants to suspend the program. The underlying assembly instruction is brk.
I wanted to add device tests support to the library, but after setting up the exception handler, if the debugger reaches a breakpoint I added manually, the debugger just hangs. To bypass this, I tried to make the exception handler forward the handling of the exception to the debugger.
When I implement an exception handler, if it returns with a failure (i.e. anything other than KERN_SUCCESS), the kernel should forward it to the next exception handler in line, the debugger in my case. I didn't find any official documentation on this, but it says so here and in a piece of code from Mike Ash's blog:
// Handle EXCEPTION_DEFAULT behavior
kern_return_t catch_mach_exception_raise (mach_port_t exception_port,
mach_port_t thread,
mach_port_t task,
exception_type_t exception,
mach_exception_data_t code,
mach_msg_type_number_t codeCnt)
{
// Do smart stuff here.
fprintf(stderr, "My exception handler was called by exception_raise()\n");
// Inform the kernel that we haven't handled the exception, and the
// next handler should be called.
return KERN_FAILURE;
}
Even if I always return KERN_FAILURE, the debugger hangs when I pause at a breakpoint. Here's a screenshot from the Variables View in Xcode, which loads indefinitely:
Is there a way to set up an exception handler and live in peace with LLDB?

Throwing an exception from anywhere in project

I want to show alert whenever an exception occured anywhere in my code.
Without enclosing the code that may throw exception.
Everytime when exception occures it shows error in main class. Is that possible to handle from there?
This is normal way of throwing an exception im using:
#try {
// code that may throw exception
}
#catch (NSException * e) {
// show alert
}
#finally {
}
However, I have found an effective work-around - creating my own exception handler (which is also useful for other reasons). First, create a function that will handle the error and output it to the console (as well as whatever else you want to do with it)
void uncaughtExceptionHandler(NSException *exception) {
NSLog(#"CRASH: %#", exception);
NSLog(#"Stack Trace: %#", [exception callStackSymbols]);
// Internal error reporting
}
Next, add the exception handler to your app delegate:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
NSSetUncaughtExceptionHandler(&uncaughtExceptionHandler);
// Normal launch stuff
}
If this doesn't work, then there are only two possible reasons:
Something is overwriting your NSSetUncaughtExceptionHandler call (there can be only one handler for your entire app). For example, some 3rd party libraries set their own uncaughtExceptionHandler. So, try setting it at the END of your didFinishLaunchingWithOptions function (or selectively disabling 3rd party libraries). Or better yet, set a symbolic break point on NSSetUncaughtExceptionHandler to quickly see who is calling it. What you may want to do is to modify your current one rather than adding another one.
You're not actually encountering an exception (for example, EXC_BAD_ACCESS is not an exception)
Navigate to Breakpoint panel in Xcode, click "plus" button at the left-bottom to add a new breakpoint, then do this:
build and run your project, Xcode will brings you to the line where exception throws.
If I'm understanding the question correctly, it seems that you're looking for a good way to handle errors in your code that may have been driven by a user action (e.g. invalid data, missing fields in a form, etc.).
The best way to do this would be to use NSError instead of NSException. NSError is generally used to pass information between different code interfaces that are expecting a possible error, where as NSException is more so for errors that are unexpected, or caused by the developer and thus, should be handled during development.
To present an alert from anywhere in your code, you could add a method to your App Delegate and use target-action to pass along your error. That might look like this:
#protocol DLErrorPresentation <NSObject>
- (void)showAlertWithError:(NSError*)error sender:(id)sender
#end
- (void)showAlertWithError:(NSError*)error sender:(id)sender {
// Your alert presentation code
}
// In some view controller or view:
NSError* error;
if(![self somethingThatMayError:error]) {
id<DLErrorPresentation> responder = [self targetForAction:#selector(showAlertWithError:sender:);
[responder showAlertWithError:error sender:self]
}
To provide additional context, here's what Apple says about exceptions in their documentation:
You should reserve the use of exceptions for programming or unexpected runtime errors such as out-of-bounds collection access, attempts to mutate immutable objects, sending an invalid message, and losing the connection to the window server. You usually take care of these sorts of errors with exceptions when an application is being created rather than at runtime.
Instead of exceptions, error objects (NSError) and the Cocoa error-delivery mechanism are the recommended way to communicate expected errors in Cocoa applications.
https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/Exceptions/Exceptions.html#//apple_ref/doc/uid/10000012i
Although exceptions are commonly used in many programming environments to control programming flow or to signify errors, do not use exceptions in this way in Cocoa and Cocoa Touch applications. Instead, you should use the return value of a method or function to indicate that an error has occurred, and provide information about the problem in an error object.
https://developer.apple.com/library/content/documentation/General/Conceptual/DevPedia-CocoaCore/ExceptionHandling.html#//apple_ref/doc/uid/TP40008195-CH18-SW1
I have added exception handling code inside my main.m.
int main(int argc, char * argv[]) {
#try {
#autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFrom
Class([AppDelegate class]));
}
} #catch (NSException *exception) {
NSLog(#"exception");
}
}
Its working now.

Is it possible to determine if an Objective-C exception will be caught?

I'm running into a situation in iOS (and OS X) where exceptions from NSKeyedUnarchiver cause binary-only third-party frameworks to crash my app (when deployed in the field) when they try to unarchive a corrupt archive, thus forcing users to delete and reinstall the app. This doesn't happen often, but I'd like that number to be zero.
I can't solve the problem by wrapping the NSKeyedUnarchiver calls, both because I don't have the source code and because those calls are not the direct result of anything that my code does; they run on arbitrary background threads at arbitrary times.
I'm currently swizzling the NSKeyedUnarchiver class so that reading a corrupt archive returns nil (as though the file were not there) rather than throwing an exception, but I can't be certain whether any of those third-party frameworks might do things correctly (with an #try/#catch block) and might break in interesting ways if I do so.
It would be helpful if I could somehow examine the Objective-C exception handling tree (or equivalent) to determine whether an exception handler would catch an exception if thrown, and if so, which handler. That way, my patched method could return nil if the exception would make it all the way up to Crashlytics (which would rethrow it, causing a crash), but could rethrow the exception if some other handler would catch it.
Is such a thing possible, and if so, how?
Why not wrap your exception-throwing callsite in a try/catch/finally?
#try {
//call to your third party unarchiver
}
#catch {
//remove your corrupted archive
}
#finally {
//party
}
Rolling your own global exception handler may also be of use here, ala: How do you implement global iPhone Exception Handling?
If you're not sure that wrapping third-party library code with #try/#catch is good enough you can hook NSKeyedUnarchiver methods to replace them with exact same wrapper thus making sure that exception is never gets thrown outside. Here is pseudo-code:
#try {
//call original NSKeyedUnarchiver implementation
}
#catch {
return nil;
}
Objc runtime has public APIs that can do such a thing

Do not stop test execution on XCTAssertThrowsSpecific

Using XCTest, a test does the following:
XCTAssertThrowsSpecificNamed([does something that breaks], NSException, NSInvalidArgumentException);
I don't want the debugger to stop on the NSException that I know will occur but I want it to still stop on other exceptions that might occur.
Basically we don't have a choice. I still consider it an xCode bug because you already told xCode an exception will be thrown, and what exception it will be. Why provide XCTAssertThrows if it's an exception you're not "expecting"?
Obviously it's for exceptions you are expecting, which #nhgrif argues should be an error, not an exception. Might be a good point, but i do want my program to crash if this case ever happens, because I can't handle it.
So the real question is more why not disable the debugger on that exception only? Probably because they haven't gotten around to it. I'd suggest opening a bug/feature request with Apple. I'd love for someone to contradict me on this (please comment!) but until then, I'll live with this issue (disable debugger when running all my tests).
--> https://stackoverflow.com/a/22393643/1701430
"Why does the test stop when the execution is thrown?"
Because you have a breakpoint, which stops execution.
"Why, after removing the breakpoint, does my application crash when the exception is thrown?"
Because you have an unhandled exception. Unhandled exceptions cause your program to crash.
"How can I handle an exception so it won't crash my program?"
The easy answer to this question is to simply NOT throw an exception. In other programming languages, like Java, this is perfectly standard. But in Objective-C, we don't really do exceptions. In Objective-C, exceptions should be saved for TRULY exceptional behavior.
With that said, and a strong suggestion for you to find another way to handle whatever it is you're trying to handle, this is how you handle an exception in Objective-C:
#try {
// code that could throw an exception
}
#catch (NSException *e) {
// handle the exception...
}
#finally {
// post try-catch code, executed every time
}

How to use XCTAssertThrowsSpecific

I'm writing Cocoa unit tests using XCTest and recently used XCTAssertThrows for the first time. That's pretty cool, but I want to make it even better with XCTAssertThrowsSpecific and requiring a certain exception.
Here is an example test:
-(void)testShortPassword {
XCTAssertThrows([user storePassword:#"abc"],#"Expect exception for short pw");
}
And on my user class I have the following code:
-(void)storePassword:(NSString*)password {
NSCAssert(password.length > 6, #"Password must be longer than 6 characters");
// go on to store the password on the keychain
}
Keeping in mind that Cocoa in general shies away from using exceptions (so it might be better to return an error, and show UI in the preceding example, etc.) How do I throw an exception in a manner that can be caught by XCTAssertThrowsSpecific? How do I specify that in XCTAssertThrowsSpecific(expression, specificException, format...)?
You should only use exceptions for exceptional cases, not for error handling and flow control
Having said that, here's how you use XCTAssertThrowsSpecific:
XCTAssertThrowsSpecific expects the specific class of the exception as the second parameter. NSCAssert throws an NSException. To test for that, use
XCTAssertThrowsSpecific([object methodThatShouldThrow], NSException, #"should throw an exception");
Now, that won't help much, because it's likely that every exception is an NSException or a subclass thereof.
NSExceptions have a name property that determines the type of the exception. In case of NSCAssert this is NSInternalInconsistencyException. To test for that, use XCTAssertThrowsSpecificNamed
XCTAssertThrowsSpecificNamed(
[object methodThatShouldThrow],
NSException,
NSInternalInconsistencyException,
#"should throw NSInternalInconsistencyException"
);

Resources