Interrupting a loop using NSNotification - objective-c

I have a class containing a method with a loop. I need to be able to break the loop if a certain event (e.g. button press) occurs.
I am using the NSNotificationCenter to notify the class containing the loop when the button is pressed.
However, If I press the button while the loop is being executed, the notification occurs after the loop is complete instead of interrupting the loop.
I'm guessing this is because it is operating in the same thread.
So how do I get the NSNotificationCenter operating in a background / different thread? Is this possible? Or is there a better way to do it?

It's not just the notification center.
I have a class containing a method with a loop. I need to be able to break the loop if a certain event (e.g. button press) occurs.
The events for that button press come in on the main thread. If your loop is running on the main thread, then the button press itself does not get processed until your loop is finished. The notification is posted immediately, relative to the button press actually getting processed by your application.
Or, in list form:
The user presses the button.
Your loop runs out of things to do and returns.
The button press arrives in your application and is turned by the button into an action message.
You post the notification.
You receive the notification.
The delay that you're seeing is between steps 1 and 2; step 4 happens immediately after step 3.
Notifications on a local (not distributed) NSNotificationCenter are dispatched on the thread you post them from, so posting it from your action method means that it will be dispatched on the main thread. This is normal and OK.
Move the loop, not the notification, to a background thread, dispatch queue, or operation queue. If you use an operation queue, you may not need the notification at all, as you can tell an operation queue to cancel all pending operations. (Your operations will need to check at any appropriate time(s) whether they have been canceled; for reasons previously discussed, killing a thread/operation at a random time is a Bad Idea.)
Background threads, blocks, and operations can communicate back to the main thread when needed (e.g., to update the UI). To send a message through the main thread's run loop, use performSelectorOnMainThread:withObject:waitUntilDone:. To dispatch a block on the main thread, use dispatch_async and dispatch_get_main_queue. To schedule an operation on the main thread, add it to [NSOperationQueue mainQueue].
For more info, read the Concurrency Programming Guide and the Notification Programming Topics.

I would run your loop in a separate thread, and have an instance variable BOOL abort;, when your button press notification comes in, set abort = TRUE; then in the loop check this value and exit if it is true.

I would run the loop in a separate thread. Even better, make it an NSOperation so that you can call [.. cancel]. Just make sure to use performSelectorOnMainThread when updating the UI from the NSOperation object. It's not a good idea to have a long running loop on the main thread.

You can't put the notification center on another thread. That object is out of your control. The problem isn't so much that they are on the same thread as that you are not allowing the run loop, which is responsible for handling the button press, to do anything. (There's one and only one run loop per thread.) As has been stated by both edsko and Peter Hosey, the button press itself, and in fact your entire UI, is stopped while your loop is running. It is generally a good idea to put long-running operations onto a background thread, then call back to the main thread to update the UI, performSelectorOnMainThread:withObject:waitUntilDone: being an easy way to do such a call back.
That said, if you were to keep the loop on the main thread, you need to let control return to the run loop periodically so that the button press will be registered. There are two ways I can think of to do this. First, you can explicitly give the run loop control briefly during each iteration of your loop:
while( !buttonWasPressed ){
// Do work...
// Let the run loop do some processing before the next iteration.
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.01]];
}
Or, you can make a single method that consists only of the code from your loop, and use performSelector:withObject:afterDelay: to have the method repeatedly called while still allowing the run loop to work:
- (void) loopTheLoop {
if( buttonWasPressed ) return;
// Do work...
// Run this method again as soon as possible.
[self performSelector:#selector(loopTheLoop)
withObject:nil
afterDelay:0.0];
}

Related

How can I allow a user adjust an NSSlider without pausing the application update loop?

NOTE: Updated below...
I have a cocoa desktop application which consists of a series of controls around a custom NSView. I am using displayLink to drive the updates.
When a user clicks on an NSControl (a slider, a button, a checkbox, a radio button) the application appears to freeze until the mouse is released. I can confirm in fact that the displayLink callback (getFrameForTime) is NOT firing during the time. If I create a timer, that also does not fire, both remain paused until the user releases the mouse, at which point the application resumes updating.
The control is bound, and if I update that value from another thread (for example, via a callback from a MIDI interface) the slider behaves as expected: it moves, the value updates and the application does not pause.
I feel like this should be a fairly obvious fix, but I'm stumped.
Checking "continuous" in IB does as advertised: sends the values continuously, but still exhibits this behavior (preventing the UI update) until the mouse is released.
This seems to be related specifically to mouseDown on NSControl? Why would this block, and do I really need to subclass all my UI elements to change this behavior (seems extreme)
DisplayLink is in its own thread, so why mouseDown on the main thread block it? If this is the case, given the injunction on updating the Cocoa UI from other than the main thread, how do I deal with it?
Any help much appreciated.
Update
Per #Nikolai's comments below, I can confirm that using an NSTimer and adding it to NSEventTrackingRunLoopMode does NOT block. However, I would really like to use CVDisplayLink which (according to the documentation) runs in it's own thread and should not be blocked in this way. Unlike CADisplayLink, I cannot find a way to explicitly assign a runloop to CVDisplayLink (it seems it doesn't work that way), so perhaps the new question should be:
Why does CVDisplayLink block on NSEventTrackingRunLoopMode?
When clicking on an NSControl the runloop mode goes from NSDefaultRunLoopMode to NSEventTrackingRunLoopMode, as long as the mouse is down. That means that only run loop sources (display link) and timers fire that have been added to this mode.
You can add timers to any mode by using -[NSRunLoop addTimer:forMode:]. For a display link the equivalent method is -[CADisplayLink addToRunLoop:forMode:].
To make your animation continue during event tracking you would do something like:
[myDisplayLink addToRunLoop:[NSRunLoop currentRunLoop]
forMode:NSEventTrackingRunLoopMode];
Your test project shows that you are calling a view's display method from within the display link's callback.
When commenting the display message out, the display link is called continuously even while moving the slider.
So what goes wrong is that when the runloop goes into event tracking mode, the call to display on the display link's thread blocks until the mouse is released and the run loop goes back to default mode. You can easily confirm this by putting a log statement before the call and one after it.
Why exactly that happens is not clear to me. What is clear is that it's illegal to call a view's methods from a background thread. You have to trigger the view's display by dispatching a setNeedsDisplay: on the main thread:
static CVReturn MyDisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeStamp* now, const CVTimeStamp* outputTime, CVOptionFlags flagsIn, CVOptionFlags* flagsOut, void* displayLinkContext)
{
dispatch_async(dispatch_get_main_queue(), ^{
[(__bridge MyCustomView*)displayLinkContext setNeedsDisplay:YES];
});
return kCVReturnSuccess;
}

Display UI, from a background thread without breaking the flow

I have a code executing in the background thread, which is performing some kind of computation and is within a do-while loop. Due to some changes in the requirements, I have to display a UI to prompt user for input. This UI code will have to be done in the main thread, and after the prompt is entered, the logic needs to continue. Using a dispatch_async on main thread, I can display the UI, but Step -2 should not continue, until the UI is done. What is the best way to accomplish this, without breaking the flow of the code and moving units into blocks?
For example:
-(void) compute
{
do
{
//calculate some data
// Step -1...
...
// Step -2
...
...
} while(flag)
}
Between Step 1 and Step 2, I want to display a prompt. What is the best way to do so? Is it okay, to block this background thread using a mutex, which will get fired, by the main thread after the UI is done?
For this I would use GCD (Grand Central Dispatch). You can easily execute a block synchronously on the main thread (or asynchronously if you prefer), using dispatch_sync (or dispatch_async). I personally use my wrapper class EX2Dispatch in my EX2Kit library (https://github.com/einsteinx2/EX2Kit), but it's the same thing.
As an example, you would do something like this:
dispatch_sync(dispatch_get_main_queue(), ^{
// Do some stuff to the UI
};
EDIT:
I was reading it as needing to display information to the user based on the earlier calculation, but if you need a response from the user before continuing, then the loop needs to break after showing the alert, then be called again.
You could use an instance variable to track how far into the loop you are, so that it can be resumed at the same point in the UIAlertView's button clicked delegate method.
Try this
dispatch_queue_t mainQueue = dispatch_get_main_queue();
dispatch_async(mainQueue, ^(void) {
//do stuff here
});

cocoa touch: Waiting for a method to finish before continuing

So I have an onclick event for a UIButton that needs to execute some code, but it can be pressed at anytime, so there is a chance that it is pressed while another method is still running. Is there a way to block the click event until the specific method is finished?
Is there a way to block the click event until the specific method is finished?
Unless the "specific method" is running on another thread, that's already what happens. The run loop can't process input events while other code is running on the main thread.

Perform on Next Run Loop: What's Wrong With GCD?

I'm trying these two approaches:
dispatch_async(dispatch_get_main_queue(),^{
[self handleClickAsync];
});
and
[self performSelector:#selector(handleClickAsync) withObject:nil afterDelay:0];
in response to a button press.
The second allows the UIButton to highlight as one would expect and perform the handleClickAsync on the next run loop (I suppose: "sometime later" for sure). The first does not allow the UIButton instance to light up until the operation is completely done.
What is the correct way to do this with GCD, or is performSelector still the only way?
I believe the answer is found here in a discussion of the main dispatch queue:
This queue works with the application’s run loop (if one is present) to interleave the execution of queued tasks with the execution of other event sources attached to the run loop.
In other words, the main dispatch queue sets up a secondary queue (alongside the standard event queue provided by UIApplicationMain() for handling blocks submitted to the main queue.
When blocks are present in the queue, the run loop will alternate dequeuing tasks from the main event queue and the dispatch queue. On the other hand, the reference for the delay parameter of -performSelector:withObject:afterDelay: notes that:
Specifying a delay of 0 does not necessarily cause the selector to be performed immediately. The selector is still queued on the thread’s run loop and performed as soon as possible.
Thus, when you use perform selector, the operation is queued at the end of the main event queue, and will not be performed until after everything in front of it in the queue (presumably including the code to unhighlight the UIButton) has been processed. When you use the main dispatch queue, however, it adds the block into the secondary queue which then likely gets processed immediately (i.e., on the next run loop) assuming there are no other blocks in the main queue. In this case, the code to unhighlight the button is still sitting in the main event queue while the run loop processes the event from the secondary block queue.
I think this will hit your point:
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
//bla bla bla
}];

Pausing a process for a pre-set (hardcoded) period of time

I am fooling around with NSTimer in a program I am writing and am having some troubles envisioning how I can do a specific task. What I want to have happen is that I want the process I am running (a method responding to a button push) to pause for a period of time and then continue. I can get the basic timer stuff to work by creating the timer in the button push method then watching it fire off and invoking a second method. However, I am not sure how I would go about pausing the button push process that spawned the timer in the first place.
Is there a way to have the button push method wait around until the timer methods fires off and tells the button push method to 'go'? Is there a queue type entry or notification type entry that I can wait on in the button push method that would be sent by the timer selector method?
Any info would be helpful.
I think you want to pause the main thread where your logic is running. Try using
[NSThread sleepForTimeInterval: 1.0]; //pauses the thread for one second