Stopping the NSApplication main event loop - objective-c

I have an application consisting of the following single .m file:
#import <Cocoa/Cocoa.h>
int main(int argc, char* argv[]) {
[[[NSThread alloc] initWithBlock: ^{
sleep(2);
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(#"Stop");
[[NSApplication sharedApplication] stop:nil];
});
}] start];
[[NSApplication sharedApplication] run];
NSLog(#"Run finished");
return 0;
}
According to the developer documentation, stop should stop the main loop (run), but it doesn't (at least not on OS X 10.12 and 10.13). There's also terminate, but this exits the program too soon. I also tried setting an NSApplicationDelegate that implements applicationShouldTerminate, but this is never called.
How can I make sure the main run loop is (cleanly) exited?
Note: The shared application main loop is necessary because there is UI work being done elsewhere. More concretely, this is giving problems in the Go WDE UI library, which uses Cocoa to provide a window to a Go application.

The documentation for -stop: says:
[C]alling this method from a timer or run-loop observer routine would not stop the run loop because they do not result in the posting of an NSEvent object.
A block dispatched to the main queue is similar in that it doesn't post an event. You can try posting an NSEventTypeApplicationDefined event after calling -stop:.

After investigating this further, it seems that the UI loop stop request is only processed after a UI event (so not just after a main loop event). So, it works in response to a UI event, but not in a thread like I did in my example.
Triggering a UI event after a stop request (e.g. a programmatic resize works for me) causes the loop to end.

Related

UI does not update when main thread is blocked in Cocoa app

I am using a NSProgressIndicator in my main thread to update on progress as I run through my entire method. Now when I end up calling an object from a different class file, and wait for that object to return to a value to my main thread, I notice that the NSProgressIndicator will disappear. I understand that this is because the main thread is blocked until I get the return value from the other object.
So my questions is what is the recommended way for updating UI in the main thread without blocking it and having other objects run in the background and return values to the main thread as needed. I know how to use blocks but blockoperations are not allowed to return values.
What I need is something that helps this pseudo code:
-(IBAction) main {
//Update progress indicator UI to show progress
//perform an call to another object from another class.
// wait till i get its return value.
//Update progress indicator UI to show progress
// Use this return value to do something.
//Update progress indicator UI to show progress
}
When the call to the other object is made, I notice that the determinate NSProgressIndicator I have completely disappears since the main thread is blocked. Thanks.
Your above code is not the correct approach. Since main never returns, the progress indicator will never update. You must return quickly on the main thread.
Instead, what you want to do is set up a background block that at various points updates the progress indicator on the main thread. So, for instance:
- (IBAction)start:(id)sender {
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
dispatch_async(dispatch_get_main_queue(), ^{[self.progress setProgress:0];});
// Doing some stuff
dispatch_async(dispatch_get_main_queue(), ^{[self.progress setProgress:.25];});
// Doing more stuff
dispatch_async(dispatch_get_main_queue(), ^{[self.progress setProgress:.75];});
});
}
(Yes, this causes the queue to retain self, but that's ok here because self is not retaining the queue.)
You can achieve what you are looking for with GCD (Grand Central Dispatch).
Here is an example to get you started:
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0ul);
dispatch_async(queue, ^{
// Perform async operation
dispatch_sync(dispatch_get_main_queue(), ^{
// Update UI
});
});
It sounds like your operation should be run in a separate thread which can be done several ways but is probably most easily achieved using NSOperationQueue and either custom NSOperation classes (it's easier than it sounds to set these up) or use of the NSInvokeOperation class.
Then you can send messages back to your class in the main thread using the NSNotificationCenter or set up as an observer using Key-Value Observing (KVO).
Bottom line, you have a variety of choices and to make the best one should have an understanding of the underlying technologies. I'd start with Apple's Threaded Programming Guide personally, then read it a second time to be sure you extracted all the goodness before building out your solution.

Running a Cocoa GUI in a non-main thread

I am having a gui/threading related problem in developing a cocoa user interface. The application is designed like this:
Main Thread (#1): parses arguments, loads plugins, etc.
Gui thread (#?): launches the gui, handles events, etc. Its the gui thread.
The Cocoa framework is non-thread safe, but enforces one rule, the GUI must run on the main thread. A assertion is used to check this. To try to go around this I implemented the run method myself (code below) following this - http://cocoawithlove.com/2009/01/demystifying-nsapplication-by.html - guide. But I am missing something. A window is opened, but stays blank (completely white). Although if I make the call in the main thread it works perfectly.
So basically I need to figure out what's missing.
- (void)run
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
[self finishLaunching];
shouldKeepRunning = YES;
do
{
[pool release];
pool = [[NSAutoreleasePool alloc] init];
NSEvent *event =
[self
nextEventMatchingMask:NSAnyEventMask
untilDate:[NSDate distantFuture]
inMode:NSDefaultRunLoopMode
dequeue:YES];
[self sendEvent:event];
[self updateWindows];
} while (shouldKeepRunning);
[pool release];
}
- (void)terminate:(id)sender
{
shouldKeepRunning = NO;
}
Don't. This approach will never work. Even if you fix your current problem (the window not drawing) you'll immediately run into another obscure, impossible-to-fix problem, and another, and another. Cocoa expects the GUI thread to be the main thread, end of story.
Do all in the background thread except updating the GUI. I see that you have only a line where you need to update the GUI. So do it the way you're doing it, except that you execute all GUI updates in the main thread:
dispatch_async(dispatch_get_main_queue(), ^
{
[self updateWindows];
});
Now I don't know what's updateWindows, I assumed that this wouldn't create a race condition.
Why not reverse the problem? Have the main thread spawn a thread (let's call this the app thread), then block before spawning the GUI. The app thread will parse arguments, load plugins, etc. After it's initialization is done, the app thread will signal the main thread to go ahead and launch the GUI.

Graceful termination of NSApplication with Core Data and Grand Central Dispatch (GCD)

I have an Cocoa Application (Mac OS X SDK 10.7) that is performing some processes via Grand Central Dispatch (GCD). These processes are manipulating some Core Data NSManagedObjects (non-document-based) in a manner that I believe is thread safe (creating a new managedObjectContext for use in this thread).
The problem I have is when the user tries to quit the application while the dispatch queue is still running.
The NSApplication delegate is being called before actually quitting.
- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender
I get an error "Could not merge changes." Which is somewhat expected since there are still operations being performed through the different managedObjectContext. I am then presented with the NSAlert from the template that is generated with a core data application.
In the Threading Programming Guide there is a section called "Be Aware of Thread Behaviors at Quit Time" which alludes to using replyToApplicationShouldTerminate: method. I'm having a little trouble implementing this.
What I would like is for my application to complete processing the queued items and then terminate without presenting an error message to the user. It would also be helpful to update the view or use a sheet to let the user know that the app is performing some action and will terminate when the action is complete.
Where and how would I implement this behavior?
Solution:
So I had a few different issues here.
I had blocks that were accessing core data in a dispatch_queue preventing my application from terminating gracefully.
When I tried to add a new item to the dispatch_queue a new instance of the dispatch_queue was started on a new thread.
What I did to solve this was use NSNotificationCenter in my AppDelegate (where (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender was being called. In the template code that Core Data generates add the following:
// Customize this code block to include application-specific recovery steps.
if (error) {
// Do something here to add queue item in AppController
[[NSNotificationCenter defaultCenter] postNotificationName:#"TerminateApplicationFromQueue" object:self];
return NSTerminateLater;
}
Then in AppController add an observer for the notification (I added this to awakeFromNib):
- (void)awakeFromNib {
NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
[center addObserver:self selector:#selector(terminateApplicationFromQueue:) name:#"TerminateApplicationFromQueue" object:nil];
// Set initial state of struct that dispatch_queue checks to see if it should terminate the application.
appTerminating.isAppTerminating = NO;
appTerminating.isTerminatingNow = NO;
}
I have also created a struct that can be checked against to see if the user wants to terminate the application. (I set the initial state of the struct in awakeFromNib above). Place the struct after your #synthesize statements:
struct {
bool isAppTerminating;
bool isTerminatingNow;
} appTerminating;
Now for the long-running dispatch_queue that is preventing the app from gracefully terminating. When I initially create this dispatch_queue, a for loop is used to add the items that need updating. After this for loop is executed, I have tacked on another queue item that will check the struct to see if the app should terminate:
// Additional queue item block to check if app should terminate and then update struct to terminate if required.
dispatch_group_async(refreshGroup, trackingQueue, ^{
NSLog(#"check if app should terminate");
if (appTerminating.isAppTerminating) {
NSLog(#"app is terminating");
appTerminating.isTerminatingNow = YES;
}
});
dispatch_release(refreshGroup);
And the method to be called when the notification is received:
- (void)terminateApplicationFromQueue:(NSNotification *)notification {
// Struct to check against at end of dispatch_queue to see if it should shutdown.
if (!appTerminating.isAppTerminating) {
appTerminating.isAppTerminating = YES;
dispatch_queue_t terminateQueue = dispatch_queue_create("com.example.appname.terminate", DISPATCH_QUEUE_SERIAL); // or NULL
dispatch_group_t terminateGroup = dispatch_group_create();
dispatch_group_async(terminateGroup, terminateQueue, ^{
NSLog(#"termination queued until after operation is complete");
while (!appTerminating.isTerminatingNow) {
// add a little delay before checking termination status again
[NSThread sleepForTimeInterval:0.5];
}
NSLog(#"terminate now");
[NSApp replyToApplicationShouldTerminate:YES];
});
dispatch_release(terminateGroup);
}
}
I haven't dealt with this myself, but just from my reading of the docs, it looks like what you should do is:
Return NSTerminateLater from applicationShouldTerminate:. This lets the system know that your app isn't ready to terminate just yet, but will do so shortly.
Enqueue a "final" block on your dispatch queue. (You need to make sure that other blocks are not enqueued after this. This block will then be run after all the other work has been performed. Note the queue must be serial -- not one of the concurrent queues) for this to work correctly.) The "final" block should do [NSApp replyToApplicationShouldTerminate:YES];, which will complete the normal termination process.
There isn't any direct way to find out whether a GCD queue is still working. The only other thing that you can do (that I know of) to handle this is to put all of the blocks into a dispatch group, and then wait on the group in applicationShouldTerminate: (using dispatch_group_wait().

When using NSURLConnection in main(), why the connection can not finished?

I'm testing the HTTPFileUploadSample now. Because I want to use it to create a type of command tool line program, so i call the method in the main() function, like this:
int main (int argc, const char * argv[])
{
#autoreleasepool {
Uploader *upl = [Uploader alloc];
[upl initWithURL:[NSURL URLWithString:#"http://localhost/uploader.php"]
filePath:#"/test.txt"
delegate:upl
doneSelector:#selector(onUploadDone)
errorSelector:#selector(onUploadError)];
//[[NSRunLoop currentRunLoop] run];
}
return 0;
}
I found it can create the connection and post request normally, but it can not finish the connection, because it do not call those delegate methods(connection:didReceiveResponse: or connection:didReceiveData: or connectionDidFinishLoading:) at all.
So I call the method [[NSRunLoop currentRunLoop] run] to run loop (as the comment in codes), then everything is ok. I do not know why. Can anybody give me some explanation? Thx!
The runloop is a big event handler infinite loop (well, infinite until it's stopped). It watches various sources and when they generate events it dispatches those events to listeners. This is a very effective way to manage asynchronous operations on a single thread.
NSURLConnection (and many other things in Cocoa) rely on the runloop for their processing. If nothing runs the runloop, then the events aren't processed.

Async call in Objective-C

I'm trying to get data from a website- xml. Everything works fine.
But the UIButton remains pressed until the xml data is returned and thus if theres a problem with the internet service, it can't be corrected and the app is virtually unusable.
here are the calls:
{
AppDelegate *appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
if(!appDelegate.XMLdataArray.count > 0){
[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
[appDelegate GetApps]; //function that retrieves data from Website and puts into the array - XMLdataArray.
}
XMLViewController *controller = [[XMLViewController alloc] initWithNibName:#"MedGearsApps" bundle:nil];
[self.navigationController pushViewController:controller animated:YES];
[controller release];
}
It works fine, but how can I make the view buttons functional with getting stuck. In other words, I just want the UIButton and other UIButtons to be functional whiles the thing works in the background.
I heard about performSelectorInMainThread but I can't put it to practice correctly.
You don’t understand the threading model much and you’re probably going to shoot yourself in the foot if you start adding asynchronous code without really understanding what’s going on.
The code you wrote runs in the main application thread. But when you think about it, you don’t have to write no main function — you just implement the application delegate and the event callbacks (such as touch handlers) and somehow they run automatically when the time comes. This is not a magic, this is simply a Cocoa object called a Run Loop.
Run Loop is an object that receives all events, processes timers (as in NSTimer) and runs your code. Which means that when you, for example, do something when the user taps a button, the call tree looks a bit like this:
main thread running
main run loop
// fire timers
// receive events — aha, here we have an event, let’s call the handler
view::touchesBegan…
// use tapped some button, let’s fire the callback
someButton::touchUpInside
yourCode
Now yourCode does what you want to do and the Run Loop continues running. But when your code takes too long to finish, such as in your case, the Run Loop has to wait and therefore the events will not get processed until your code finishes. This is what you see in your application.
To solve the situation you have to run the long operation in another thread. This is not very hard, but you’ll have to think of a few potential problems nevertheless. Running in another thread can be as easy as calling performSelectorInBackground:
[appDelegate performSelectorInBackground:#selector(GetApps) withObject:nil];
And now you have to think of a way to tell the application the data has been loaded, such as using a notification or calling a selector on the main thread. By the way: storing the data in the application delegate (or even using the application delegate for loading the data) is not very elegant solution, but that’s another story.
If you do choose the performSelectorInBackground solution, take a look at a related question about memory management in secondary threads. You’ll need your own autorelease pool so that you won’t leak autoreleased objects.
Updating the answer after some time – nowadays it’s usually best to run the code in background using Grand Central Dispatch:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// No explicit autorelease pool needed here.
// The code runs in background, not strangling
// the main run loop.
[self doSomeLongOperation];
dispatch_sync(dispatch_get_main_queue(), ^{
// This will be called on the main thread, so that
// you can update the UI, for example.
[self longOperationDone];
});
});
Use NSURLConnection's connectionWithRequest:delegate: method. This will cause the specified request to be sent asynchronously. The delegate should respond to connection:didReceiveResponse: and will be sent that message once the response is completely received.
You can make use of a background operation that gets pushed into the operation queue:
BGOperation *op = [[BGOperation alloc] init];
[[self operationQueue] addOperation:op];
[op release];
I've created specific "commands" that get executed in the background:
#implementation BGOperation
# pragma mark Memory Management
- (BGOperation *)init
{
if ((self = [super init]) != nil)
/* nothing */;
return self;
}
- (void)dealloc
{
self.jobId = nil;
[super dealloc];
}
# pragma mark -
# pragma mark Background Operation
- (void)main
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
[appDelegate GetApps];
[pool release];
return;
}
#end
After completion it might be a good idea to send a notification to the main thread because the internal database has been changed.
It looks as if you might be using NSURLConnection inside your getApps method. If so, you should convert it to an asynchronous call.