How to add multiple timer to a thread - objective-c

I'm trying to add multiple timers to a thread, not the main thread.Here is code:
- (IBAction)addTimer:(id)sender
{
if (!_timerQueue) {
_timerQueue = dispatch_queue_create("timer_queue", NULL);
}
dispatch_async(_timerQueue, ^{
NSTimer *tempTimer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:#selector(timerAction) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:tempTimer forMode:NSRunLoopCommonModes];
[[NSRunLoop currentRunLoop] run];
});
}
The method above is triggered by a button action. But the code in the dispatch block runs only once not matter how many times i click the button. So only one Timer in that thread. I wonder why?

The reason why you only see one timer at a time is in the last line of your dispatch block:
-[NSRunLoop run] is a blocking call that returns when the last input source of the run loop finishes and no timers are scheduled anymore.
In addition, GCD-queues are strictly FIFO and you are creating a serial queue.
Thus, the result of you tapping that button several times is a queue that gets fuller and fuller without the first block ever finishing:
Since the timer is repeating, there always is something scheduled on the run loop and thus run never returns, barring all subsequent blocks from ever being invoked.

Related

How to wake up thread after [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:distantFuture]?

I have a Download object which handles NSURLConnection.
Then I have NSOperation object (DownloadOperation) which holds Download object as property.
Download object has ability to start/pause/resume/cancel.
This is the main method of DownloadOperation
- (void)main
{
#autoreleasepool {
BOOL isDone = NO;
if (![self isCancelled]) {
[_download start]; //Download object start (creates NSURLConnection internally)
}
NSDate *distantFuture = [NSDate distantFuture];
while(!isDone && ![self isCancelled]) {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:distantFuture];
if (!_download.isActive) { //internal state of Download
isDone = YES;
}
}
[self completeOperation]; //this sets finished and executing flags
}
}
From outside (from UI), I manipulate with Download object: Start, Pause, Resume, Cancel.
And internally I am changing its state so when Download is finished or canceled, isActive is set to NO and while loop should end.
This works if I start Download and let it finish (in background, NSURLConnection finished and called delegate -connectionDidFinish...)
If I pause/resume Download, download will continue to download and finish (and change its internal state: isActive -> NO).
On pause I cancel NSURLConnection and on resume I create new.
Or if I cancel the download, it will also be inactive (NSURLConnection is canceled).
But here is the problem:
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:distantFuture]; never returns in those cases (when I cancel NSURLConnection) and my "if" statement is never handled so this DownloadOperation is always considered running and will never exit my NSOperationQueue.
It looks like there is no event that could be fired that would cause that runloop to wake up.
I tried
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.05]];
and this kind of works but not so smooth and I don't think it is the best solution.
What I really want to know is, how to force that NSRunLoop to wake up (how to fire some event that will cause it to wake up) and continue my while loop?
Here's what I did.
Good thing is that I have notifications in my app and I know exactly when downloads are changing. So I've made an instance variable NSThread currentThread and at the beginning of main I call currentThread = [NSThread currentThread].
After I receive notification which I know should cause my thread to wake up I call:
[self performSelector:#selector(wakeUpThread:) onThread:currentThread withObject:nil waitUntilDone:NO];
- (void)wakeUpThread does nothing, it is empty method, but purpose of this is to wake up thread and cause my run loop to continue.

Avoid blocking UI when doing a lot of operations that are required to be on main thread

I need to do a possibly long series of calls that must occur on the main thread (because otherwise UIKit will balk). By "long" I mean 10,000 operations lasting .1 second each on an iPad 3.
Obviously, It's probably not the best idea to just loop through all of them at once.
I don't know how to execute all these on the main thread while leaving enough breathing room to keep UIKit responsive and the watchdog asleep (ie. not get terminated for hogging the run loop).
Does anybody have an idea? I will be targeting iOS 5.
Specifically what I'm trying to do is cache UITextPositions, because a UITextView is apparently taking a non-cached, iterative approach at getting UITextPositions, which means it is very, very slow at doing positionFromPosition:textview.beginningOfDocument offset:600011, but much faster at getting positionFromPosition:aPositionAt600000 offset:11. In fact, in my test case, the former takes over 100 seconds (on the main thread!), while the latter is virtually instantaneous.
Why do you want to do it on the main thread? The typical answer is to do these operations on a background thread, and send UI updates back to the main thread. For example, you could use Grand Central Dispatch:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// do my time consuming task and everytime it wants to update the UI,
// it should dispatch that back to the main queue, e.g.
for (NSInteger i = 0; i < 10000; i++)
{
// do my background work
// now update the UI
dispatch_async(dispatch_get_main_queue(), ^{
// update the UI accordingly
});
}
});
Update:
It sounds like you have to do this in the foreground, so perhaps using a NSTimer might be better. I'm not a big NSTimer guy, but it might look something like the following.
First, make sure you have a class instance variable for it:
NSTimer *_timer;
Next, you can initialize it with:
- (void)startTimer
{
_timer = [NSTimer timerWithTimeInterval:0.0 target:self selector:#selector(timerCallback:) userInfo:nil repeats:YES];
NSRunLoop *runloop = [NSRunLoop currentRunLoop];
[runloop addTimer:_timer forMode:NSDefaultRunLoopMode];
}
This will then invoke the timerCallback, perhaps processing a single UITextPosition on each invocation:
- (void)timerCallback:(NSTimer*)theTimer
{
BOOL moreTextPositionsToCalculate = ...;
if (moreTextPositionsToCalculate)
{
// calculate the next UITextPosition
}
else
{
[self stopTimer];
}
}
and when you're done, you could stop your timer like so:
- (void)stopTimer
{
[_timer invalidate];
_timer = nil;
}

Invalidating a NSTimer from within its invocation method

I have scheduled a timer using [NSTimer scheduledTimerWithTimeInterval:target:selector:userInfo:] and want to invalidate it at some point when it fires.
- (id)init
{
[NSTimer scheduledTimerWithInterval:1 target:self selector:#selector(fired:) userInfo:nil repeats:YES];
}
- (void)fired:(NSTimer *)timer
{
if (someCondition) {
[timer invalidate];
}
}
Is this allowed? The documentation states
You must send this message from the thread on which the timer was installed. If you send this message from another thread, the input source associated with the timer may not be removed from its run loop, which could prevent the thread from exiting properly.
If this is not a proper way to accomplish this task: What is the proper way?
Calling [timer invalidate] from within the fire method is just fine, that code will be executed in the same thread as the one used when you created the timer.
The Apple Doc you quoted only warns you that if you create a separate thread and invalidate the timer from it, then, and only then, should expect unpredictable behaviour.
Ex.
// Create the background queue
dispatch_queue_t queue = dispatch_queue_create("do not do this", NULL);
// Start work in new thread
dispatch_async(queue, ^ {
// !! do not do this !!
if (someCondition) {
[yourTimer invalidate];
}
// or this
[self fire:yourTimer];
});
// won’t actually go away until queue is empty
dispatch_release(queue);
It is fine to invalidate it from the fired method, as the fired method is on the same thread the timer was scheduled on:
scheduledTimerWithTimeInterval:target:selector:userInfo:repeats:
Creates and returns a new NSTimer object and schedules it on the current run loop in the default mode.

Keep NSThread alive and run NSRunLoop on it

So I'm starting a new NSThread that I want to be able to use later by calling performSelector:onThread:.... From how I understand it calling that methods add that call to the runloop on that thread, so on its next iteration it will pop all these calls and subsequently call them until there is nothing left to call. So I need this kind of functionality, an idle thread ready for work that I just can call upon it. My current code looks like this:
- (void)doInitialize
{
mThread = [[NSThread alloc] initWithTarget:self selector:#selector(runThread) object:nil];
[mthread start];
}
- (void)runThread
{
NSAutoReleasePool *pool = [[NSAutoReleasePool alloc] init];
// From what I understand from the Google machine is that this call should start the
// runloop on this thread, but it DOESN'T. The thread dies and is un-callable
[[NSRunLoop currentRunLoop] run];
[pool drain];
}
- (void)scheduleSomethingOnThread
{
[self performSelector:#selector(hardWork) onThread:mThread withObject:nil waitUntilDone:NO];
}
But the thread is not kept alive, and the performSelector:onThread does not do anything. How do I go about this the right way?
A run loop requires at least one "input source" to run. The main run loop does, but you have to add a source manually to get a secondary run loop's -run method to do anything. There's some documentation on this here.
One naïve way to get this to work would be just to put [[NSRunLoop currentRunLoop] run] in an infinite loop; when there's something to do, it'll do it, and return immediately otherwise. The problem is that the thread will take a decent amount of processor time simply waiting for something to occur.
Another solution is to install an NSTimer on this run loop to keep it alive.
But, if possible, you should use a mechanism designed for this sort of thing. If possible, you may want to use NSOperationQueue for background operations.
this piece of code should force the thread to wait forever
BOOL shouldKeepRunning = YES; // global
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
[runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode]; // adding some input source, that is required for runLoop to runing
while (shouldKeepRunning && [runLoop runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]); // starting infinite loop which can be stopped by changing the shouldKeepRunning's value

Objective C: App freezes when using a timer

It took me hours to figure out how to implement a timer into my program, but when it runs, the app doesn't load completely as it did before the timer.
In my main.m:
int main (int argc, const char * argv[]) {
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
OutLauncher *theLauncher = [[OutLauncher alloc] init];
NSTimer *theTimer = [theLauncher getTimer];
[theTimer retain];
[[NSRunLoop currentRunLoop] addTimer: theTimer forMode: NSDefaultRunLoopMode];
[[NSRunLoop currentRunLoop] run];
[pool release];
return 0;
}
The file OutLauncher is being imported into that, which looks like this:
- (void)doStuff {
NSLog( #"Doing Stuff");
}
- (NSTimer *)getTimer{
NSTimer *theTimer;
theTimer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector: #selector(doStuff) userInfo:nil repeats:YES];
return [theTimer autorelease];
}
The timer works, the console updates every second with the phrase "doing stuff" but the rest of the program just won't load. It will if I comment out the code I added to int main though
A few things:
You don't need to autorelease the timer you return after setting one up with [NSTimer scheduledTimerWithTimeInterval:] It is already autoreleased.
The timer created via scheduledTimerWithInterval is already added to the default run loop. So you don't need to use the following:
[[NSRunLoop currentRunLoop] addTimer: theTimer forMode: NSDefaultRunLoopMode];
[[NSRunLoop currentRunLoop] run];
In fact, you don't even need to keep a reference to the timer unless you need to cancel it yourself.
Here is what apple has to say about what you are doing in the documentation
run
Puts the receiver into a permanent
loop, during which time it processes
data from all attached input sources.
(void)run Discussion If no input sources or timers are attached to the
run loop, this method exits
immediately; otherwise, it runs the
receiver in the NSDefaultRunLoopMode
by repeatedly invoking
runMode:beforeDate:. In other words,
this method effectively begins an
infinite loop that processes data from
the run loop’s input sources and
timers.
Manually removing all known input
sources and timers from the run loop
is not a guarantee that the run loop
will exit. Mac OS X can install and
remove additional input sources as
needed to process requests targeted at
the receiver’s thread. Those sources
could therefore prevent the run loop
from exiting.
If you want the run loop to terminate,
you shouldn't use this method.
Instead, use one of the other run
methods and also check other arbitrary
conditions of your own, in a loop. A
simple example would be:
BOOL shouldKeepRunning = YES;
// global NSRunLoop *theRL =
[NSRunLoop currentRunLoop]; while
(shouldKeepRunning && [theRL
runMode:NSDefaultRunLoopMode
beforeDate:[NSDate distantFuture]]);
where shouldKeepRunning is set to NO
somewhere else in the program.
Availability Available in iOS 2.0 and
later.
So it looks like your code is doing what it is supposed to do. It is Logging all the timer events and waiting indefinitely for the run loop.
It looks like you're making it a lot more complicated than it needs to be. You don't need to put any code in your main.m file. If you want to fire the doStuff method every second, this is all the code you need:
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector: #selector(doStuff) userInfo:nil repeats:YES];
You don't need to (auto)release it yourself. timer is already autoreleased. If you want to be able to cancel the timer, you will need to keep a reference of it. Then when you want to cancel, you just call invalidate and set the reference to nil.