How would I go ahead about pausing a method without freezing the UI, and then continuing the process when told?
Thanks,
Kevin
There's not a way to interrupt execution of an arbitrary method and to then resume it later.
You can break your method into multiple pieces that represent the units of work that you want to be able to pause between. You can then use timers (either NSTimers or [NSObject performSelector:withObject:afterDelay:] for a specific pause interval.
More information on what you're trying to do might allow for some more specifically useful suggestions here.
There is a method to prevent the UI from stopping and eg. timers too. This is more or less the whole reason why we have run loops (well, thats not really true but in your case it is).
Just insert this into your function and everything will work:
while(pauseFunction)
{
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.1f]];
}
Remark: Everything scheduled with the runloop keeps running, this is the UI, timers, networking etc. pp.
Related
I'm getting to know the NS/Objective-C model of concurrency. Say I have a command line tool that does something like this:
#include "myLibrary.h"
void callback(void* parameter){
cout<<"callback called.\n";
//some logic...
}
int main(int argc, char* argv[]){
myLibraryInit(callback);
std::string s;
while(true){
cin>>s;
myLibrarysResponseTo(s);
}
}
In my library, I'd like to be able to have two responses. One which starts a repeating timer and one which stops it. The timer should call the callback supplied to the library by myLibraryInit.
I've used NSTimers before in iPhone/iPad apps, and I think the problem stems from the different paradigm command line tools have. The main thread goes into main and never finishes it until the program is finished. This means it's not free to run the main run loop, which is what gets the timer going. I think. So how do I make an NSTimer work in this context?
The other thing is that Apple NSTimer documentation says I need to invalidate an NSTimer on the same thread it was installed. I don't know how to figure out what thread I was on when I installed the timer, and then keep track of it (and ensure it stays alive) until I want to invalidate the timer. I'm not sure if I'm just missing an obvious mapping between threads and dispatch queues, run loops, or something else. I am using core bluetooth and I initialize a central manager like so:
_centralManager=[[CBCentralManager alloc]
initWithDelegate: self
queue: dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
];
so a callback may be triggered from here. If the callback includes some logic to call the library function that stops the timer, I can't guarantee from which thread came the invalidate. So how do I properly invalidate the timer?
I found this question but it doesn't allow a main to happen at the same time as the run loop that that the timer is on.
I hope I gave enough context. Thanks in advance for your replies.
You must call dispatch_main() or run an NSRunLoop in the main thread if any of the system frameworks [that use GCD or asynchronous operations] are to work correctly.
This can be as simple as calling [[NSRunLoop currentRunLoop] run]; at the end of your main() function (just make sure you schedule the kickoff work first as that method never returns).
In my application at some point I have a bunch of messages scheduled using performSelector.
Under some conditions, while handling an UI action, I need to wait for all the currently scheduled selectors to fire.
I could place my code in another method and schedule it using performSelector:target:argument:order:modes: with order value high enough to be sure it will fire last, but there are reasons why I think that would make an ugly solution.
So I send [[NSRunLoop currentRunLoop] runUntilDate:[NSDate date]] and it seems to work just like I need it to.
Still, I'm not sure if that is a legitimate way to let the run loop roll for just one iteration. Also, are there any potential troubles associated with this approach?
Okay, answering my own question.
First of all it's a duplicate (also, this).
Next, generally, sending [[NSRunLoop currentRunLoop] runUntilDate:[NSDate date]] from within the same run loop is a bad idea:
In theory, autorelease pool will get drained that way. In practice, I've not been able to make my app crash by using objects allocated pre-runUntilDate (under ARC), but better not to risk anyway.
If somehow another action gets dequeued during that runUntilDate it might cause some unexpected side effects.
TL;DR I should do myself a favor and replace that piece of code with something more deterministic.
I'm using NSOperationQueue, and NSOperation for running some function on background click.
But I want to be able, when user clicks some button, stop that Operation.
How can I do it?
Something like, [currentoperation stop];
Cancel - won't work me. I want to stop immediately.
Thanks
You should be calling the -cancel method, and the operation itself has to support being cancelled by monitoring the isCancelled property/keypath and safely stopping when its value becomes YES. If the NSOperation is your own, you will probably have to create a custom subclass to implement this functionality. You cannot (safely) force an arbitrary operation to immediately stop. It has to support being cancelled.
You can't stop immediately by using anything Apple provides with NSOperation. You can use -[cancel] as other people have suggested here, but the current operation will still run until completion. One way of getting close to use -[isCancelled] inside of your operation and sprinkle that throughout the code (especially in long running loops). Something like:
- (void)main {
// do a little work
if ([self isCancelled]) { return; }
// do a little more work
if ([self isCancelled]) { return; }
}
This way you'll get things stopped relatively soon.
If you're looking to really force the thread to stop, you may need to look into signal handling. There's a threaded example here. Sending a custom signal to a specific thread, you may be able to then terminate that thread in some way. This will be a lot more work, though, and is probably much more trouble than it's worth.
you use cancel, and test whether self (the NSOperation) has been cancelled during execution.
I'm trying to set up a thread that stays idle until new data it's available. What it's the best approach for this in Objective-C? Till now I tried to make a simple run loop
while(YES) {
if(isDataAvailable) {
//process data
}
}
However this has an huge impact on performance, my FPS drops from 40 to 20 and the interface becomes unusable (even if the actual data process happens once in a second or so and it's not very intense for the CPU. I tried to add [NSThread sleepForTimeInterval:0.01] at the end, but this way I lose data packages ('process data' refers to some streaming related operations, queue and unqueue data packages), however the FPS returns to normal.
I'm fair new in Objective-C and I was thinking maybe there is a better way to do this? I also had a look over NSRunLoop, but didn't manage to make it work as a run loop :), only attached a timer to it that doesn't do more than my [NSThread sleepForTimeInterval:0.01] thing.
Any help it's highly appreciated:D
If you need to keep the seconary thread alive, you definitely want to use a real runloop:
http://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/Multithreading/RunLoopManagement/RunLoopManagement.html#//apple_ref/doc/uid/10000057i-CH16-SW1
Basically, just create and start your thread, set up an autorelease pool, then run your runloop for some set time amount. When the time expires, you check to see if you should exit your thread, or enter into the runloop again.
As Marcelo points out though, there are more modern approaches to achieve concurrency (GCD and async dispatch being a couple of examples) so maybe investigate other forms of concurrency as well.
The Problem
I have an NSOperationQueue called logEntryGeneratorQueue
I want to wait until all operations on the queue have completed
If I use:
[logEntryGeneratorQueue waitUntilAllOperationsAreFinished];
it works fine if the thread adding to the queue is in the background itself.
However, if I'm running this code via a unit test, it'll be running on the main thread. So I
came up with this "solution", which I really don't like:
if ([NSThread isMainThread]) {
while ([[logEntryGeneratorQueue operations] count] > 0) {
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:1]];
}
} else {
[logEntryGeneratorQueue waitUntilAllOperationsAreFinished];
}
This was always less than ideal, but has always worked fine on 10.5. However, now I've upgraded my project to using the 10.6 SDK, and this breaks.
On one test, it actually quit the test before it completed. I've no idea why - I assume it's something to do with the way NSOperationQueues work differently in 10.6 - they now use GCD.
What I've Tried
I've tried replacing the runUntilDate with sleep, which, as I thought, means every test pauses forever when it gets here.
My Question
Is there a better way to wait for an NSOperationQueue to finish on a main thread? If not, how can I get this code working under 10.6?
The Solution
I realised that my code was in an eternal loop because I was calling mergeChangesFromContextDidSaveNotification on the main thread whilst also waiting for the queue to finish on the main thread. And since the merge changes was called after waitUntilAllOperationsAreFinished, it never got executed.
I think the answer is to change where I run NSOperationQueues from. I shouldn't run an NSOperationQueue that deals with core data stuff on the main thread. And I shouldn't really be running this intensive stuff on the main thread for performance reasons anyway I guess.
I'd say that waitUntilAllOperationsAreFinished should work as expected on 10.6, no matter from what thread it's called. Since operation queues in 10.6 no longer use the run loop, there is no point in not blocking and having the loop run. Did you try to just call waitUntilAllOperationsAreFinished=
I agree with Max: -waitUntilAllOperationsAreFinished should work. Is your queue -suspended ?
IMHO you need to consider the possibility that waitUntilAllOperationsAreFinished may hang if one (or all) of its operations are progressing using main app thread as a carrier. Example: your nsoperation is not concurrent and uses glkview auto-update loop for animation and updating own state and your operation is only done (and operation is marked finished) only if main thread has a chance to work. But it can't as it is blocked in waiting for finishing these operations.