I have a Cocoa document-based app and running into some problems with NSUndoManager even though I'm having Core Data take care of all of it.
So every time a new persistent document is created I create a root context and a child context. The root context is responsible for writing to the persistent document and the child context is the context that I modify via create/edit/delete NSManagedObjects. I set the root context as the parent context. I also set the undo manager of the child context to the original context that was created with the NSPersistentDocument. Here's some code to hopefully make it a bit clearer:
// create root context
NSManagedObjectContext *rootContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
// startContext is the context created with the document
[rootContext setPersistentStoreCoordinator:[startContext persistentStoreCoordinator]];
NSManagedObjectContext *childContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
// set root as parent
[childContext setParentContext:rootContext];
// set undo
[childContext setUndoManager:[startContext undoManager]];
The reason I do all of this is because I ran into a similar problem as described here:
Enable saving of document NSManagedObjectContext immediately?
So I bring this up because this is the only code in my application where I even touch the NSUndoManager. I am testing my app by simply inserting NSManagedObjects and then Undo-ing the insertion. Sometimes after two undos, or maybe five, or maybe even ten I receive the following error:
_endUndoGroupRemovingIfEmpty:: NSUndoManager 0x100159f30 is in invalid state, endUndoGrouping called with no matching begin
2013-01-29 21:31:23.375 TestApplication[30125:303] (
0 CoreFoundation 0x00007fff8a54f0a6 __exceptionPreprocess + 198
1 libobjc.A.dylib 0x00007fff8215f3f0 objc_exception_throw + 43
2 CoreFoundation 0x00007fff8a54ee7c +[NSException raise:format:] + 204
3 Foundation 0x00007fff80ea021f -[NSUndoManager _endUndoGroupRemovingIfEmpty:] + 195
4 Foundation 0x00007fff80ea0154 -[NSUndoManager endUndoGrouping] + 42
5 Foundation 0x00007fff80ed79da +[NSUndoManager(NSPrivate) _endTopLevelGroupings] + 447
6 AppKit 0x00007fff8253632d -[NSApplication run] + 687
7 AppKit 0x00007fff824dacb6 NSApplicationMain + 869
8 TestApplication 0x0000000100001512 main + 34
9 TestApplication 0x00000001000014e4 start + 52
So if I'm reading the debugging information correctly then I need to call [[context undoManager] beginUndoGrouping] however the problem with this is that no where in my program do i use the `[[context undoManager] endUndoGrouping]'. Has anyone experience this before?
Any help is appreciated.
Each context in OSX creates an undoManager by default (nil in iOS). The reason for the grouping error was that your child context undoManager was trying to nest changes inside the rootContext's undoManager which was nested inside the startContext undoManager (which was the same as the child context's undoManager).
// create root context
NSManagedObjectContext *rootContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
// startContext is the context created with the document
[rootContext setPersistentStoreCoordinator:[startContext persistentStoreCoordinator]];
[rootContext setUndoManager:startContext.undoManager];
NSManagedObjectContext *childContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
// set root as parent
[childContext setParentContext:rootContext];
For me, it seems to be caused by sending begin/endGrouping message during undo/redo handling.
I disable undo registration temporarily to remove this error message.
[[NSNotificationCenter defaultCenter] addObserverForName:NSUndoManagerDidUndoChangeNotification object:undoManager queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
[undoManager disableUndoRegistration];
// Some code for undo containing undo grouping
[undoManager enableUndoRegistration];
}
I did some more digging and found this.
In my project some sorting operation is done in a child managed object context running on a background thread and I tried to undo the whole process including the sorting one in the main thread (top level context). I could resolve this by moving the sorting operation to the background thread as it was during undo/redo.
Related
After updating to 10.10.3 the WebView component started to crash after dealloc
- (void)dealloc {
[self.webView.windowScriptObject setValue:nil forKey:#"CocoaApp"];
[[self.webView mainFrame] stopLoading];
[self.webView setUIDelegate:nil];
[self.webView setEditingDelegate:nil];
[self.webView setFrameLoadDelegate:nil];
[self.webView setPolicyDelegate:nil];
[self.webView removeFromSuperview];
}
The crash happens somewhere deep in WebView
EXC_BAD_ACCESS
1 0x7fff910bae9e WebDocumentLoaderMac::detachFromFrame()
2 0x7fff920288c0 WebCore::FrameLoader::detachFromParent()
3 0x7fff910d0e55 -[WebView(WebPrivate) _close]
4 0x7fff910d0c49 -[WebView dealloc]
5 0x7fff8b1cf89c objc_object::sidetable_release(bool)
6 0x7fff8b1b5e8f (anonymous namespace)::AutoreleasePoolPage::pop(void*)
7 0x7fff912b26f2 _CFAutoreleasePoolPop
8 0x7fff8830e762 -[NSAutoreleasePool drain]
9 0x7fff8e3f0cc1 -[NSApplication run]
10 0x7fff8e36d354 NSApplicationMain
11 0x1000ebb12 main
12 0x7fff8c81e5c9 start
13 0x3
Any ideas? Is this a Apple bug? It started AFTER 10.10.3?
It doesn't crash when NSZombie is enabled!
I noticed you're using your own policy delegate:
[self.webView setPolicyDelegate:nil];
There's a known bug related to policy delegates in WebKit (only very recently fixed):
https://bugs.webkit.org/show_bug.cgi?id=144975
The short version is that you're probably hitting this assertion (which crashes the process with an intentional segfault):
https://github.com/WebKit/webkit/blob/24b1ae89efc10a4e6a6057b429c8e1d8d138a32f/Source/WebCore/loader/DocumentLoader.cpp#L935
because your policy handler (i.e. decidePolicyForMIMEType:request:frame:decisionListener:) is failing to make a policy decision (i.e not use, ignore, or download). The decision hangs around unmade, and when the loader eventually detaches it asserts that there are no pending policy decisions, which fails since the view is still waiting for a decision.
The fix i made, is not to release the webview, but hold a static reference into it (this is far from solving it and i contacted Apple regarding this issue)
#warning HOTFIX
{
//this is because of http://stackoverflow.com/questions/29746074/osx-10-10-3-crashes-webview-on-dealloc
static NSMutableArray * LIVE_FOR_EVER_WEBVIEW;
if (LIVE_FOR_EVER_WEBVIEW == nil) {
LIVE_FOR_EVER_WEBVIEW = [NSMutableArray new];
}
if (self.webView) {
[LIVE_FOR_EVER_WEBVIEW addObject:self.webView];
}
}
I'm using MBPogressHUD in my project and I have all the libraries, imports, etc. set up correctly. However, I'm getting a crash that seems like it has to do with the selector methods. I have a method called "getInfo" that basically connects to a server. The HUD is triggered by pressing a button. After doing some research on the crash, people said to put the initialization in viewWillAppear because some of the init time takes up the time it takes to actually do the task and show the HUD.
-(void)viewWillAppear:(BOOL)animated {
connectionHUD = [[MBProgressHUD alloc]initWithView:self.view];
[self.view addSubview:connectionHUD];
connectionHUD.labelText = #"Connecting";
connectionHUD.mode = MBProgressHUDModeAnnularDeterminate;
}
-(IBAction)connectButton:(id)sender {
[connectionHUD showWhileExecuting:#selector(getInfo) onTarget:self withObject:nil animated:YES];
}
Crash:
Tried to obtain the web lock from a thread other than the main thread or the web thread. This may be a result of calling to UIKit from a secondary thread. Crashing now...
-(void)getInfo {
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
UserPi *newPi = [[UserPi alloc]init];
newPi.passWord = self.passTextField.text;
[defaults setObject:self.passTextField.text forKey:#"password"];
newPi.userName = self.userTextField.text;
[defaults setObject:self.userTextField.text forKey:#"username"];
newPi.ipAddress = self.ipTextField.text;
[defaults setObject:self.ipTextField.text forKey:#"ip"];
[newPi connectToServer];
NSString* newAddress = [newPi returnIP];
self.connected = newPi.connected;
[self.delegate sendIP:newAddress];
[self.delegate isConnected:self.connected];
[defaults synchronize];
[self.navigationController popToRootViewControllerAnimated:YES];
}
full error:
bool _WebTryThreadLock(bool), 0x7327740: Tried to obtain the web lock from a thread other than the main thread or the web thread. This may be a result of calling to UIKit from a secondary thread. Crashing now...
1 0x3a1ffe9 WebThreadLock
2 0x4ec8ff -[UITextRangeImpl isEmpty]
3 0x4ec4db -[UITextRange(UITextInputAdditions) _isCaret]
4 0x48e7b6 -[UITextSelectionView setCaretBlinks:]
5 0x328f79 -[UIKeyboardImpl setCaretBlinks:]
6 0x3185bc -[UIKeyboardImpl setDelegate:force:]
7 0x3184ae -[UIKeyboardImpl setDelegate:]
8 0x53ff65 -[UIPeripheralHost(UIKitInternal) _reloadInputViewsForResponder:]
9 0x29215b -[UINavigationController navigationTransitionView:didStartTransition:]
10 0x418961 -[UINavigationTransitionView transition:fromView:toView:]
11 0x418658 -[UINavigationTransitionView transition:toView:]
12 0x294651 -[UINavigationController _startTransition:fromViewController:toViewController:]
13 0x29489b -[UINavigationController _startDeferredTransitionIfNeeded:]
14 0x295dc6 _popViewControllerNormal
15 0x296065 -[UINavigationController _popViewControllerWithTransition:allowPoppingLast:]
16 0xe6124b0 -[UINavigationControllerAccessibility(SafeCategory) _popViewControllerWithTransition:allowPoppingLast:]
17 0x2961a8 -[UINavigationController popViewControllerWithTransition:]
18 0x2965b9 -[UINavigationController popToViewController:transition:]
19 0x296257 -[UINavigationController popToRootViewControllerWithTransition:]
20 0x2961de -[UINavigationController popToRootViewControllerAnimated:]
21 0x4868 -[ConnectionViewController getInfo]
22 0x126a6b0 -[NSObject performSelector:withObject:]
23 0x8657 -[MBProgressHUD launchExecution]
24 0xca1805 -[NSThread main]
25 0xca1764 __NSThread__main__
26 0x95108ed9 _pthread_start
27 0x9510c6de thread_start
Most UIKit framework methods are not thread safe and must always be called on the main thread. In your case, getInfo is calling UIKit APIs from a background thread notably, -[UINavigationController popToRootViewControllerAnimated:]
MBProgressHUD causes your getInfo to be called on a background thread. See the method showWhileExecuting:onTarget:withObject:animated:.
Try using GCD to dispatch the UIKit methods on the main thread:
-(void)getInfo {
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
UserPi *newPi = [[UserPi alloc]init];
newPi.passWord = self.passTextField.text;
[defaults setObject:self.passTextField.text forKey:#"password"];
newPi.userName = self.userTextField.text;
[defaults setObject:self.userTextField.text forKey:#"username"];
newPi.ipAddress = self.ipTextField.text;
[defaults setObject:self.ipTextField.text forKey:#"ip"];
[newPi connectToServer];
NSString* newAddress = [newPi returnIP];
self.connected = newPi.connected;
// **NOTE**
// PS: self.delegate methods should not call UIKit methods
// if they do, then move them into the main thread callback block
[self.delegate sendIP:newAddress];
[self.delegate isConnected:self.connected];
[defaults synchronize];
// Do UI work on main thread.
dispatch_async(dispatch_get_main_queue(), ^{
[self.navigationController popToRootViewControllerAnimated:YES];
});
}
Are you calling -getInfo on the main thread? [self.navigationController popToRootViewControllerAnimated:YES]; must be called on the main thread.
The problem is that your getInfo method makes calls into the UI, like this:
[self.navigationController popToRootViewControllerAnimated:YES];
… but you're running it on a background thread.
The whole point of -showWhileExecuting: onTarget: withObject: animated: is that it automatically runs your code in a background thread.
So, you need to protect things the same way as you do when manually running in a background thread.
So, any UI code in your method needs to use performSelectorOnMainThread: and friends, or dispatch_async or other means of doing the same thing.
In this particular case, you want to dispatch a method that takes a primitive (BOOL) argument, which means you can't just use -performSelectorOnMainThread: withObject:. But you also presumably want to wait until it's done, which means you can't just use dispatch_async.
You can just write a wrapper method that takes no arguments:
- (void)popNavigationControllerToRoot {
[self.navigationController popToRootViewControllerAnimated:YES];
}
… and then:
[self performSelectorOnMainThread:#selector(popNavigationControllerToRoot)
waitUntilDone:YES];
Or you can use wrappers around NSInvocation, like the ones here:
[[self.navigationController dd_invokeOnMainThreadAndWaitUntilDone:YES]
popToRootViewControllerAnimated:YES];
I have an ios 6 app which instantiates 3 singletons in the App Delegate as below:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
Constants *constSingleton = [Constants getSingleton];
EntryContainerSingleton * eSingle = [EntryContainerSingleton getSingleton];
LocationMgrSingleton *loc = [LocationMgrSingleton getSingleton];
return YES;
}
However the problem happens that all three calls are executing in different threads simultaneously. EntryContainerSingleton depends on Constants to do some tasks. But Constants is not instantiated completely when it is executing those tasks.
How can I handle this situation?
I was googling around and in previous versions of iOS people have used the NSOperation queue to do this.
However, Im not sure whether this is a good approach in iOS 6. And even if it is I haven't used NSOperation queue before and all the samples on the web are from previous versions and are instantiated in some class which is not an APP Delegate.
If someone can give me some sample code on how to do this in App Delegate to get me started Id really appreciate it
EDIT
Entries controller singleton
-(id)init
{
self = [super init];
NSLog(#"%s %d", __PRETTY_FUNCTION__, __LINE__);
[self getEntriesFromServer];
..............
.............
constSingleton = [Constants getSingleton];
[self addSelfAsObserverToNotifications];
return self;
}
Inside entriescontrollersingleton
-(void)getEntriesFromServer
{
NSLog(#"%s %d", __PRETTY_FUNCTION__, __LINE__);
if(!constSingleton)
{
NSLog(#"constSingleton is null");
}
__block NSString *dateString1 = [constSingleton getCurrentDateTime];
NSLog(#"Sending notification: Async Call started successfully at time %#", dateString1);
[[NSNotificationCenter defaultCenter] postNotificationName:#"didStartAsyncProcess"
object:self];
.......
}
Console output
[96894:c07] -[AppDelegate application:didFinishLaunchingWithOptions:] 21
[96894:c07] +[Constants getSingleton] 39
[96894:c07] -[Constants init] 65
[96894:c07] -[EntryContainerSingleton init] 75
[96894:c07] -[EntryContainerSingleton getEntriesFromServer] 154
[96894:c07] constSingleton is null
[96894:c07] Sending notification: Async Call started successfully at time (null)
[96894:c07] -[Constants incrementNumBackgroundNetworkTasksBy1:] 87
If the Entries singleton needs access to the Constants singleton, it should call [Constants getSingleton] to obtain it. Make sure you implement your singleton methods correctly (see Objective-C Singleton implementation, am i doing it right?)
Every time you need access to a singleton object, you should call [ClassName getSingleton]. There shouldn't be any reason to store a singleton as an instance variable in your application delegate.
I'm currently banging my head around this problem:
I have two views on the same entity. The first one lets the user CRUD the Entity (TouchModelVariable), the second one lets the user assign it to a another entity (TouchModelConstraintTerm). The second view is only for selection. But when I delete entities via the first view, upon scrolling in the second view the app crashes with an "index out of bounds" error.
Detailed explanation: First the first controller, the CRUD one.
MSPUIManagedDocument *doc = self.document;
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entityDescription = [NSEntityDescription entityForName:MODEL_ENTITY_TOUCH_MODEL_VARIABLE inManagedObjectContext:doc.managedObjectContext];
fetchRequest.entity = entityDescription;
fetchRequest.fetchBatchSize = 20;
//[NSComparisonPredicate predicateWithLeftExpression:[NSExpression expressionForKeyPath:#"touchModel.active"] rightExpression:[NSExpression expressionForConstantValue:YES] modifier:NSDirectPredicateModifier type:nil options:nil];
// touchModel.active == 1
fetchRequest.predicate = [NSPredicate predicateWithFormat:#"touchModel == %#", self.touchModel, nil];
NSSortDescriptor *sortDescriptior = [NSSortDescriptor sortDescriptorWithKey:#"name" ascending:YES selector:#selector(caseInsensitiveCompare:)];
NSArray *sortDescriptors = [NSArray arrayWithObject:sortDescriptior];
fetchRequest.sortDescriptors = sortDescriptors;
// nameSectionIndex
NSFetchedResultsController *fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:doc.managedObjectContext sectionNameKeyPath:#"name" cacheName:#"MSPVariablesManagementTableViewController"];
fetchedResultsController.delegate = self;
NSError *error = nil;
[NSFetchedResultsController deleteCacheWithName:#"MSPVariablesManagementTableViewController"];
if(![fetchedResultsController performFetch:&error]) {
NSLog(#"Error while fetching <%#>, <%#>", error, [error userInfo]);
}
return fetchedResultsController;
The TableView is bound to that controller, as it's described in the book "Pro CoreData for iOS".
The second view uses nearly the same code, but the CacheKeys are different. If I delete entities with the first view, and then navigate through the app to the second view (which is re-instantiated every time) the app crashes upon scrolling, because it assumes that the deleted records are there.
I also tried to save the ManagedObjectContext, before entering the second view.
CRASH: *** -[_PFArray objectAtIndex:]: index (8) beyond bounds (8)
2012-03-17 11:36:48.953 MSPLPSolve[38685:fb03] Stack Trace: (
0 CoreFoundation 0x0192503e __exceptionPreprocess + 206
1 libobjc.A.dylib 0x01dc2cd6 objc_exception_throw + 44
2 CoreFoundation 0x018cda48 +[NSException raise:format:arguments:] + 136
3 CoreFoundation 0x018cd9b9 +[NSException raise:format:] + 57
4 CoreData 0x003ffc23 -[_PFArray objectAtIndex:] + 131
5 CoreData 0x004ec260 -[NSFetchedResultsController objectAtIndexPath:] + 448
6 MSPLPSolve 0x0005d820 -[MSPUtilitiesVariablesSelectionTableViewController configureCell:cellForRowAtIndexPath:] + 128
7 MSPLPSolve 0x0003057b -[MSPFetchedResultsTableViewController tableView:cellForRowAtIndexPath:] + 283
8 UIKit 0x008efc54 -[UITableView(UITableViewInternal) _createPreparedCellForGlobalRow:withIndexPath:] + 494
9 UIKit 0x008f03ce -[UITableView(UITableViewInternal) _createPreparedCellForGlobalRow:] + 69
10 UIKit 0x008dbcbd -[UITableView(_UITableViewPrivate) _updateVisibleCellsNow:] + 1350
11 UIKit 0x008ea6f1 -[UITableView layoutSubviews] + 242
12 UIKit 0x00893d21 -[UIView(CALayerDelegate) layoutSublayersOfLayer:] + 145
... a load of other stuff not in my code ...
BUT when I save the document completly (closing, reopening) between deleting entities via the first controller and navigating to the second controller - it doesn't crash. Is NSFetchedResultsController caching a loads of stuff in the background? And is there a in depth guide how the caching works and how I have to use it?
Puuuh, thanks for reading this far! Regardless if you have an idea or not!
Mark
Did you do a performFetch on your results controller after you altered the model? Also I'm assuming your results controllers delegate methods are implemented correctly.
This question follows on from my other question on why my app isn't being brought down by exceptions.
The Problem
When an exception is thrown on the main thread via an Action, the app still doesn't crash.
As per Dave's answer to my original question, I've implemented the reportException category on NSApplication and set the uncaught exception handler.
Code
I've got the following in my app delegate, which I've hooked up to a button in my UI to test.
-(IBAction)crashOnMainThread:(id)sender {
[self performSelectorOnMainThread:#selector(crash) withObject:nil waitUntilDone:YES];
}
-(void)crash {
// To test out the exception handling
[NSException raise:NSInternalInconsistencyException format:#"This should crash the app."];
}
When I press the button, my app doesn't crash. When I look at the console log, I see this:
06/09/2010 14:12:25 EHTest1[26384] HIToolbox: ignoring exception 'This should crash the app.' that raised inside Carbon event dispatch
(
0 CoreFoundation 0x00007fff80ab4cc4 __exceptionPreprocess + 180
1 libobjc.A.dylib 0x00007fff819560f3 objc_exception_throw + 45
2 CoreFoundation 0x00007fff80ab4ae7 +[NSException raise:format:arguments:] + 103
3 CoreFoundation 0x00007fff80ab4a74 +[NSException raise:format:] + 148
4 EHTest1 0x00000001000010e3 -[EHTest1_AppDelegate crashLapsus] + 63
5 Foundation 0x00007fff88957c25 -[NSObject(NSThreadPerformAdditions) performSelector:onThread:withObject:waitUntilDone:modes:] + 234
6 Foundation 0x00007fff8896ad48 -[NSObject(NSThreadPerformAdditions) performSelectorOnMainThread:withObject:waitUntilDone:] + 143
7 EHTest1 0x0000000100001030 -[EHTest1_AppDelegate crashOnMainThread:] + 60
8 AppKit 0x00007fff85c7e152 -[NSApplication sendAction:to:from:] + 95
9 AppKit 0x00007fff85ca26be -[NSMenuItem _corePerformAction] + 365
** Snip **
It looks like Carbon is catching the exception, which is really annoying.
This suggests that for any action code, you need to immediately run it in a background thread so any exceptions are registered as uncaught. Huh? I've not seen any code that's structured like this.
What I've tried
Crashing the app with a delay so it's not connected to a UI element. It crashes fine.
I've tried installing a custom NSExceptionHandler using this in my app delegate:
-(BOOL)exceptionHandler:(NSExceptionHandler *)sender
shouldHandleException:(NSException *)exception
mask:(unsigned int)aMask {
abort();
return YES;
}
-(void)applicationWillFinishLaunching:(NSNotification *)aNotification {
NSExceptionHandler *handler = [NSExceptionHandler defaultExceptionHandler];
[handler setExceptionHandlingMask:NSLogAndHandleEveryExceptionMask];
[handler setDelegate:self];
}
the problem here is it crashes on every exception, whether it's caught or not.
If I try and check the mask and don't crash on a caught exception, I'm back to square 1 as it seems that HIToolbox catches the exception in exactly the same way as a try/catch block would.
Questions
How can I stop HIToolbox catching the exceptions so that my app uses the uncaught exception handler and crashes?
Is it OK to be running code that's in the same call stack as an action? Surely this is OK?
If it's not OK, what's the alternative?
This is driving me up the wall, so any help would be much appreciated.
I answered your last question on this subject, and ran into the same problem with Carbon's HIToolbox catching exceptions thrown by IBActions.
First, undo everything I mentioned in my previous answer. It doesn't work with IBActions for some reason. My hunch is that HIToolbox lives lower on the exception-handling-chain, and gets any IBAction/GUI exceptions before NSApplication has the opportunity to. Any custom exception-handling function you can register with NSSetUncaughtExceptionHandler() lives (I believe) at the top of the chain.
You're on the right track with NSExceptionHandling:
Add the ExceptionHandling.framework to your Xcode Project
#import "ExceptionHandlerDelegate.h" into your AppDelegate.m (or a custom Singleton exception class)
Inside AppDelegate.m:
// Very first message sent to class
+ (void)initialize
{
NSExceptionHandler *exceptionHandler = [NSExceptionHandler defaultExceptionHandler];
unsigned int handlingMask = NSLogAndHandleEveryExceptionMask;
[exceptionHandler setExceptionHandlingMask:handlingMask];
[exceptionHandler setDelegate:self];
// ...
}
#pragma mark -
#pragma mark NSExceptionHandler Delegate Methods
// Called 1st: Should NSExceptionHandler log the exception?
- (BOOL)exceptionHandler:(NSExceptionHandler *)sender shouldLogException:(NSException *)exception mask:(unsigned int)aMask
{
// ...log NSException if desired...
return NO; // Changes to YES if NSExceptionHandler should handle logging
}
// Called 2nd: Should NSExceptionHandler handle the exception?
- (BOOL)exceptionHandler:(NSExceptionHandler *)sender shouldHandleException:(NSException *)exception mask:(unsigned int)aMask
{
// ...crash your app here (e.g. [NSApp terminate:self]; )
// ...or handle the NSException and return YES/NO accordingly
return NO; // If app crashed, never gets returned - should crash before that
}
The NSLogAndHandleEveryExceptionMask flag tells NSExceptionHandler to capture every exception it can (for your app only, I believe), regardless of where on the exception chain it exists.
This means that #catch/#try/#finally blocks won't work, because the NSHandleOtherExceptionMask flag tells NSExceptionHandler to capture "everything below it" on the exception-handler chain. You can remove that flag, but then HIToolKit will probably get any IBAction exceptions again, since it appears to be lower on said chain.
Apple's docs have info about the flags: NSExceptionHandler docs
So when an NSException is raised (anywhere in your app AFAIK), and NSLogAndHandleEveryExceptionMask is set, these are called in the delegate in-order:
-exceptionHandler:shouldLogException:mask: is called first.
-exceptionHandler:shouldHandleException:mask: is called second.
Just put your "crash code" inside the second delegate method and you should be OK.
Helpful article: Understanding Exceptions and Handlers in Cocoa
The reason I think you were having trouble getting NSExceptionHandler's delegate to work is because it's NOT compatible with a custom method set with NSSetUncaughtExceptionHandler(), which was part of the answer in my previous question. Per Apple:
The NSExceptionHandler class provides
facilities for monitoring and
debugging exceptional conditions in
Objective-C programs. It works by
installing a special uncaught
exception handler via the
NSSetUncaughtExceptionHandler
function. Consequently, to use the
services of NSExceptionHandler, you
must not install your own custom
uncaught exception handler.
It also probably doesn't work well when you override NSApplication's -reportException: method.
Lastly, there doesn't appear to be a need to use #catch/#try/#finally (also part of my previous answer). Configuring NSExceptionHandler inside +initialize appears to "kick in" immediately, unlike overriding NSApplication's -reportException: method.
You can’t reliably. Throwing exceptions across API boundaries is not supported unless explicitly documented (and I can’t think of any cases that are explicitly documented).