How to implement custom NSApplication terminate: behavior in Cocoa? - cocoa

I'm attempting to implement custom termination behavior in a Cocoa application. Normally when my application exits gracefully, it performs final-runtime database cleanup and then exits. This occurs inside AppDelegate (delegate of NSApplication) whenever [NSApp terminate:aSender] is called:
- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender
{
// database cleanup...
return NSTerminateNow;
}
If an error occurs during runtime (e.g. the database file was deleted), I present the error to the user, and give them the option to Recover (put the file back and try again), or to Quit. If Quit is selected, I want to exit the app skipping database cleanup all-together since it's no longer possible. In essence, I'd like something like this:
- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender
{
BOOL gracefulTermination = ...;
if (gracefulTermination == YES)
{
// Database cleanup....
}
return NSTerminateNow;
}
The problem, of course, is getting the value for gracefulTermination.
Is there a way to pass a custom variable to NSApp when terminate: is called, such as an infoDict, and then receive it inside applicationShouldTerminate:?
If not, is there a better way to accomplish custom termination behavior?
As far as I know, when terminate: is called by some other object, this happens:
[NSApp terminate:self]; is called by foo (a.k.a. self).
NSApp sends its delegate: [aDelegate applicationShouldTerminate:self]; (self is NSApp, not foo).
aDelegate receives the message and executes applicationShouldTerminate: if implemented.
foo appears to disappear somewhere, and by the time aDelegate gets the message, it's gone for good and only NSApp appears as sender. This prevents me from passing an infoDict inside of foo, or just a plain infoDict, to aDelegate containing custom terminate: behavior.
I am aware that it's possible to exit without using [NSApp terminate:...] with something like exit(). Though from what I've read this is frowned down upon since it's not kosher for Cocoa. Plus it would also prevent any other cleanup operations from occurring inside applicationShouldTerminate:, operations which shouldn't be skipped even during a non-graceful exit.

The ideal solution would be to structure your app in such a way that the app delegate can tell whether it's allowed to terminate.
Assuming it isn't possible for your application delegate to access this information any other way (say, which object triggered termination affects whether it can happen), this seems like the simplest solution: Subclass NSApplication, give it a terminationInfo property and override terminate: to set this property and call super.

Related

A way to schedule a method call in Cocoa?

I'd like to call a method
-(void) fooWithObject:(MyObject *)obj;
On a given time (NSDate instance)
without blocking the main thread.
What is the best way to go about this? I've seen NSTimer, performSelector and NSOperation but reading a bit of each just made me more confused.
Important:
Ability to pass in an object to the method I'm calling
A way to set time of execution
Non blocking
Don't need repeats
Cheers!
performSelector:withObject:afterDelay can be used for this. The delay is relative so you need to compute the difference between your desired date and the current time. Something like this (typed directly into answer on an iPad - E&OE):
- (void) fooWithObject:(MyObject *)obj onDate:(NSDate *)onDate
{
[self performSelector:#selector(fooWithObject:)
withObject:obj
afterDelay:MAX(0, [onDate timeIntervalSinceNow])
];
}
Note that MAX is used in case the passed in date is in the past - the delay will be 0 and the selector will therefore be performed "as soon as possible".

Design strategy to save in the background with MagicalRecord

Recently I started a new app requiring just one store (no document based app). For some time I was quite happy thinking I could finally get rid of throwing around the NSManagedObjectContext... until I wanted to save in the background :-(
Now I am confused about my own code. For example:
- (void)awakeFromInsert
{
[super awakeFromInsert];
[self resetCard];
self.creationDate = TODAY;
self.dictionary = [Dictionary activeDictionary];
NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
[center postNotificationName:NOTE_NEWCARD object:self];
}
[Dictionary activeDictionary] is a NSManagedObject static function returning a pointer to a NSManagedObject created in the main thread. That will cause a cross/context error during the background save. Because my program always read from the same store, I thought I could avoid writing this:
[Dictionary activeDictionaryWithContext:...]
I suppose that with MagicalRecord, as long as I work always with the same backend is is possible to avoid passing the context pointer. Which function should I use to get that context?
[NSManagedObjectContext MR_defaultContext]
[NSManagedObjectContext MR_context]
[NSManagedObjectContext MR_contextForCurrentThread]
In the example the object sends itself within a notification, something almost granted to cause more conflicts.
In the case of the notification should I always send only the objectID?
It seems to me that my objects should issue side effect operations/notifications only if they are running in the main context. However some of those side operations change my object graph creating new instances of other entities.
Can I safely omit the two problematic function calls I have mentioned if I save with [MagicalRecord MR_saveAll] ?
Should I assume that the objects of the new background saving context will be an exact copy of the ones in my main thread without calling those extra functions?
Now I am having problems because I never expected awakeFromInsert to run several times for the same object of the same store. I was thinking about something like this:
- (void)awakeFromInsert
{
[super awakeFromInsert];
if ([self managedObjectContext] == [NSManagedObjectContext MR_defaultContext]) {
[self resetCard];
self.creationDate = TODAY;
self.dictionary = [Dictionary activeDictionary];
NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
[center postNotificationName:NOTE_NEWCARD object:self];
}
}
That should make my awakeFromInsert code run only once, but not in the background saving context. I am concerned about losing information if I do so
While you can certainly send your object in a notification that way, I would recommend against that. Remember, even with the new parent-child contexts in CoreData, NSManagedObjects are NOT thread safe. If you create or import objects, you will need to save them prior to using them in another context.
MagicalRecord provides a relatively simple API for background saving:
[MagicalRecord saveInBackgroundWithBlock:^(NSManagedObjectContext *localContext){
MyEntity *newEntity = [MyEntity MR_createInContext:localContext];
//perform other entity operations here
}];
This block does all the work for you, without worrying about setting up the NSManagedObjectContext properly.
Another reason you should not pass NSManagedObjects across a notification is that you do not know what thread the notification will be received on. This can potentially lead to a crash, because, again, NSManagedObjects are NOT thread safe.
Another alternative to the notification approach you present is to add an observer to NSManagedObjectContextDidSaveNotification, and merge your changes on that notification. This will fire only after your objects are saved, and are safe for crossing contexts through either the parent-child relationship, or the persistent store (the old way).

Cocoa: NSTimer gdb when invalidating

I'm using some timers and want the user to be able to cancel it before it's done, or remove it from a view after it has finished. Both of these things are supposed to be handled by a cancelAction method. If the timer hasn't finished, an if statement in cancelAction tells the timers to invalidate, makes them nil and removes the view displaying the time left. This works perfectly. If the timers already finished, cancelAction only removes the view, and does not try to invalidate them. But this doesn't seem to work. If I cancel before they finish, everything works, If I cancel after they finished, it throws an exception saying unrecognized selector sent to instance. Both timer were made using the scheduledTimerWithTimeInterval:target:selector:userInfo:repeats: method
The difference between calling before or after they finished, is that if they finished, this method is run.
- (void)timerFinished:(id)sender {
[timer invalidate];
timer = nil;
[timerLabelUpdater invalidate];
timerLabelUpdater = nil;
}
This is where it gets strange. If I just remove [timerLaberUpdater invalidate] the method gets called and it gets removed. I don't want to remove that line because that timer counts down the time left and updates the view with the time left. If I don't invalidate it, it keeps counting down to the negatives, plus it takes up memory.
I sincerely have no idea why removing that line changes anything, both timers were called in the same way, and if I remove the other line, it still doesn't work, ONLY by removing that instruction does anything change.
I get EXC BAD ACCESS in gdb, which I think means the selector is sent to an instance that was released.
Thanks for the help.
if([timerLabelUpdater isValid])
[timerLabelUpdater invalidate];

When NSDocument Asynchronous Saving is enabled, is there any point in calling -unblockUserInteraction in a simple -dataOfType:error:?

I have a straight-forward, Mac OS X, Cocoa, Document-based application which uses the new 10.7 Autosave, Versions and Asychronous Saving APIs. I am fully using the NSDocument APIs to get all of Apple's Document-based application features for free.
In order to support the new Lion Autosave/Versions/AsyncSaving, I have overridden the following methods in my NSDocument subclass like so:
#implementation MyDocument
...
+ (BOOL)autosavesInPlace { return YES; }
- (BOOL)canAsynchronouslyWriteToURL:(NSURL *)URL ofType:(NSString *)type forSaveOperation:(NSSaveOperationType)op {
return YES;
}
I have also overridden -dataOfType:error: to help implement saving the document's data to disk:
- (NSData *)dataOfType:(NSString *)typeName error:(NSError **)outErr {
NSData *data = nil;
if ([typeName isEqualToString:MY_SUPPORTED_TYPE_NAME]) {
data = makeSnapshotCopyOfMyDocumentData(); // assume return value is autoreleased
} else if (outErr) {
*outErr = [NSError errorWithDomain:NSOSStatusErrorDomain code:unimpErr userInfo:nil];
}
// not sure this is doing much good, since i take no action after this.
[self unblockUserInteraction];
return data;
}
...
#end
See how I'm calling -unblockUserInteraction at the end there?
When supporting the new 10.7 AsyncSaving feature, Apple advises calling -unblockUserInteraction as early as possible (after making a snapshot copy of your document's data) in your -dataOfType:error: implementation. But Apple's example showed them doing much more work after calling -unblockUserInteraction.
However, considering I take no other action after this, I'm wondering if there's any point in calling -unblockUserInteraction there at all.
So my questions:
Considering I take no other action after it, is my call to -unblockUserInteraction doing any good?
Do the Apple Frameworks just call -unblockUserInteraction immediately after -dataOfType:error: returns anyway? Should I just leave it to them?
I just noticed a subtle wording difference between the NSDocument documentation and the comment in NSDocument.h:
Docs:
If saveToURL:ofType:forSaveOperation:completionHandler: is writing on
a non-main thread because
canAsynchronouslyWriteToURL:ofType:forSaveOperation: has returned YES,
but it is still blocking the main thread, this method unblocks the
main thread. Otherwise, it does nothing.
Header:
If -saveToURL:ofType:forSaveOperation:completionHandler: is writing on
a non-main thread because
-canAsynchronouslyWriteToURL:ofType:forSaveOperation: has returned YES, but is still blocking the main thread, unblock the main thread.
Otherwise, do nothing.
I assume the Header is more up to date.
I am working on an application that calls unblockUserInteraction after the last line that has to run on the main thread. (At least that's the way I understood it)
I think our code fits the scenario that Apple had in mind when designing the async saving part of NSDocument:
in fileWrapperOfType: we ...
create a QL preview for our file wrapper (that has to run on the
main thread) ...
unblockUserInteraction ...
... "long" running file saving task (involving compression)

Updating UI in Cocoa from another .m file

I have a GUI app that has a main thread and then I use NSOperation to run 2 other threads once the user clicks the Start button. Now one thread calculates a certain value and updates it. What I want thread 2 to do is to pick this value up and update the UI.
How do I get a IBOutlet Textfield value to get updated on the UI from this second thread ?
eg:
main.m --- handles the UI and has code to start the 2 threads when the user hits the Start Button.
thread1.m -- calculates a particular value and keeps doing it until the user hits stop.
thread2.m - Need to use this thread to update the UI in main.m with the the value that thread1.m calculates.
I am unable to accomplish the thread2.m task and update the UI. My issue is that how do I define a IBOutlet and update it with a value from thread2/1 so that the main.m has access to this value and updates the UI. I have access to the actual variable in main.m and can print it out using NSLog. Its just that I am getting stuck on how to update the UI with this value. As I need to have theIBOutlet in main.m to tie it with the UILabel in the app. Any ideas guys ? Thanks.
Could you add pointers to your thread1.m and thread2.m files? Then set them with either a constructor method or some accessor methods?
If I understand the situation you described in your example, and assuming what you are calculating is an int (you can modify as you need):
Add an accessor to thread1.m
-(int)showCurrentCalcValue
{
//Assume that you get calculatedValue from whereever else in your thread.
return calculatedValue;
}
Then add to thread2.m
NSTextField *guiTextField;
Thread1 *thread1;
-(void) setThread: (Thread1 *aThread)
{
self.thread1 = aThread;
}
-(void) setGuiTextField: (NSTextField *aTextField)
{
self.guiTextField = aTextField;
}
-(void) updateGUI()
{
[guiTextField setStringValue: [thread1 showCurrentCalcValue]];
}
Presuming your main.m is something like the following:
IBOutlet NSTextField *outputDisplay
-(void) setUpThreads()
{
Thread1 *thread1 = [[Thread1 alloc] init];
Thread2 *thread2 = [[Thread2 alloc] init];
[thread2 setGuiTextField: outputDisplay];
[thread2 setThread: thread1];
//Whatever else you need to do
}
Then just take care of setting everything and calling the methods in your threads.
Source code files don't matter. You could have all of this stuff in one file (not that that would be a good idea) and the problem would be unchanged. What matters are the classes.
Classes are not simply bags of code; you design them, you name them, and you define each class's area of responsibility. A class and/or instances of it do certain things; you define what those things are and aren't.
When writing NSOperation subclasses, don't worry about the threads. There's no guarantee they even will run on separate threads. Each operation is simply a unit of work; you write an operation to do one thing, whatever that may be.
eg: main.m --- handles the UI and has code to start the 2 threads —
operations
— when the user hits the Start Button.
thread1.m -- calculates a particular value and keeps doing it until the user hits stop.
That's not one thing; that's an indefinite sequence of things.
thread2.m - Need to use this thread to update the UI in main.m with the the value that thread1.m calculates.
You should not touch the UI from (what may be) a secondary thread. See the Threading Programming Guide, especially the Thread Safety Summary.
I don't see why this should even be threaded at all. You can do all of this much more easily with an NSTimer running on the main thread.
If it would be inappropriate to “calculate… a particular value” on the main thread, you could make that an operation. Your response to the timer message will create an operation and add it to your computation queue. When the user hits stop, that action will go through on the main thread; invalidate the timer and wait for the queue to finish all of its remaining operations.
With either solution, “thread2.m” goes away entirely. Your update(s) to the UI will (and must) happen entirely on the main thread. With the latter solution, you don't even have to wait until you're done; you can update the UI with current progress information every time you receive the timer message.

Resources