-(void)viewDidLoad{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[NSTimer scheduledTimerWithTimeInterval:0.10
target:self
selector:#selector(action_Timer)
userInfo:nil
repeats:YES];
}
);
}
-(void)action_Timer{
LOG("Timer called");
}
action_Timer is not being called. I dont know why. Do you have any idea?
You're calling +[NSTimer scheduledTimerWithTimeInterval:...] from a GCD worker thread. GCD worker threads don't run a run loop. That's why your first try didn't work.
When you tried [[NSRunLoop mainRunLoop] addTimer:myTimer forMode:NSDefaultRunLoopMode], you were sending a message to the main run loop from a GCD worker thread. The problem there is NSRunLoop is not thread-safe. (This is documented in the NSRunLoop Class Reference.)
Instead, you need to dispatch back to the main queue so that when you send the addTimer:... message to the main run loop, it's done on the main thread.
-(void)viewDidLoad {
[super viewDidLoad];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSTimer *timer = [NSTimer timerWithTimeInterval:0.10
target:self
selector:#selector(action_Timer)
userInfo:nil
repeats:YES];
dispatch_async(dispatch_get_main_queue(), ^{
[[NSRunLoop mainRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
});
});
}
Realistically, there's no reason to create the timer on the background queue if you're going to schedule it in the main run loop. You can just dispatch back to the main queue to create and schedule it:
-(void)viewDidLoad {
[super viewDidLoad];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(#"on background queue");
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(#"on main queue");
[NSTimer scheduledTimerWithTimeInterval:0.10
target:self
selector:#selector(action_Timer)
userInfo:nil
repeats:YES];
});
});
}
Note that both of my solutions add the timer to the main run loop, so the timer's action will run on the main thread. If you want the timer's action to run on a background queue, you should dispatch to it from the action:
-(void)action_Timer {
// This is called on the main queue, so dispatch to a background queue.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
LOG("Timer called");
});
}
You have to add the timer to the main run loop for the timer to fire, but first, you should hold reference to to the timer in a private ivar or a property:
-(void)viewDidLoad{
// on the main queue
self.mytimer = [NSTimer scheduledTimerWithTimeInterval:0.10
target:self
selector:#selector(action_Timer)
userInfo:nil
repeats:YES];
[[NSRunLoop mainRunLoop] addTimer:myTimer forMode:NSDefaultRunLoopMode];
}
-(void)action_Timer{
dispatch_async( dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void){
LOG("Timer called");
});
}
I find easier to get off the main queue in the called method. At somepoint, maybe in viewDidUnlod or in dealloc, you will have call [self.myTimer invalidate]; self.myTimer = nil;.
NSTimer
NSRunLoop
NSObject
Related
I am adding
[self performSelector:#selector(showLyrics) withObject:nil afterDelay:20];
but if user restart the song then the this selector should not get performed. So I just want to
know how I can cancel that. Because after 20 second it will get invoked but I don't want that, and reschedule
[self performSelector:#selector(showLyrics) withObject:nil afterDelay:20];
I'v so many
[self performSelector:#selector(showLyrics) withObject:nil afterDelay:2];
I want to cancel all those, which I've scheduled before.
[[NSRunLoop currentRunLoop] cancelPerformSelector:#selector(showLyrics)
target:self
argument:nil];
You can use NSTimer instead of performSelector:withObject:afterDelay:
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:20 target:self selector:#selector(showLyrics) userInfo:nil repeats:NO];
To cancel:
[timer invalidate];
But you may want to invalidate before you start each time or keep timers in an array and iterate through them to cancel all of them.
Use an NSTimer and save a reference to it instead of performSelector. Afaik performSelector can't be cancelled. Edit: Apparently it can be cancelled, see omz's answer...
self.showLyricsTimer = [NSTimer scheduledTimerWithTimeInterval:20.0
target:self
selector:#selector(showLyrics)
userInfo:nil
repeats:NO];
To cancel the timer you use:
[self.showLyricsTimer invalidate];
But be careful to also invalidate the timer when your view disappears f.e. in the viewWillDisappear callback, since NSTimer retains it's target.
Cancels perform requests previously registered with performSelector:withObject:afterDelay:
+ (void)cancelPreviousPerformRequestsWithTarget:(id)aTarget selector:(SEL)aSelector object:(id)anArgument
It seems this is what you are looking for or what you were looking for~~
This question already has answers here:
NSTimer not firing when runloop is blocked
(2 answers)
Closed 8 years ago.
I use a NSTimer which fires every second and updates a label which displays the remaining time until an event.
I works fine so far. The problem is while I am scrolling the TableView my Label does not update, because the MainThread is blocked by the touch/scroll event.
I thought about creating a second thread for the Timer but I couldn't update the label from a background thread anyways. I had to queue it with performSelector... on the MainThread where it would stuck like before.
Is there any way to update the label while scrolling?
The problem is that a scheduledTimer will not get called while the main thread is tracking touches. You need to schedule the timer in the main run loop.
So instead of doing
[NSTimer scheduledTimerWithTimeInterval:1.0f target:self selector:#selector(updateLabel:) userInfo:nil repeats:YES];
use
NSTimer* timer = [NSTimer timerWithTimeInterval:1.0f target:self selector:#selector(updateLabel:) userInfo:nil repeats:YES];
[[NSRunLoop mainRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
try this:
self.timer = [NSTimer timerWithTimeInterval:1.0 target:self selector:#selector(updateClock) userInfo:nil repeats:YES];
[[NSRunLoop mainRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];
You could also use GCD. So run
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0ul);
dispatch_async(queue, ^{
[NSTimer scheduledTimerWithTimeInterval:1.0f target:self selector:#selector(updateLabel:) userInfo:nil repeats:YES];
});
and now in your updateLabel method
- (void) updateLabel:(id) sender {
NSString *text = #"some text";
dispatch_sync(dispatch_get_main_queue(), ^{
label.text = text;
});
}
This will update the label in the main thread.
I'll go straight to the problem.
This ones eating my head since a week.
What i intend to do is set my timer, which is supposed to be fired on main run loop, from a secondary thread. So i do it as follows.
if(timerRefresh)
{
//[timerRefresh invalidate];
timerRefresh = nil;
}
if (!self.isConnectionAvailable) {
timerRefresh = [NSTimer timerWithTimeInterval:appDelegate.TimerInterval target:self selector:#selector(startAutoRefresh) userInfo:nil repeats:NO];
}
else if (self.isLivePresent||self.isUpcomingMatchToday) {
timerRefresh = [NSTimer timerWithTimeInterval:appDelegate.TimerInterval target:self selector:#selector(startAutoRefresh) userInfo:nil repeats:NO];
}
else {
timerRefresh = [NSTimer timerWithTimeInterval:LongRefresh target:self selector:#selector(startAutoRefresh) userInfo:nil repeats:NO];
}
NSRunLoop *runLoop = [NSRunLoop mainRunLoop];
[runLoop addTimer:timerRefresh forMode:NSDefaultRunLoopMode];
[runLoop run];
When this fires, a loader begins loading on the main thread, and the processing work is done on the secondary thread.
I hope this is a correct way.
Now i have a child class within this main class which also has to show a loader while it triggers a filtering process, so to avoid multiple loaders, when the the filtering process triggers, i pause the refreshing on this parent class, by sending it notifications from the child class..like this...
-(void)teamNameClicked:(id)sender
{
BOOL result = YES;
NSNumber *newNumber = [NSNumber numberWithBool:result];
[[NSNotificationCenter defaultCenter] postNotificationName:#"PauseMatchesLiveMatchTimer" object:newNumber];
[self performSelector:#selector(sendTeamNameClickToFunction:) withObject:sender];
}
and when operation completes i have another notifier as this...
-(void)processTeamNameClick:(id)sender
{
UIButton *button = (UIButton *)sender;
selectedIndexDropDown = button.tag;
[self parseTeamFile:button.tag];
self.lblDropDown.text = [dictTeamFilter valueForKey:[NSString stringWithFormat:#"%i",button.tag]];
[tblResults performSelectorOnMainThread:#selector(reloadData) withObject:nil waitUntilDone:NO];
BOOL result = NO;
NSNumber *newNumber = [NSNumber numberWithBool:result];
[[NSNotificationCenter defaultCenter] postNotificationName:#"PauseMatchesLiveMatchTimer" object:newNumber];
}
Notice the YES and NO for results..
Now this is an observer for the notification...
-(void)pauseAndResumeTimer:(NSNotification *)notification;
{
NSNumber *newNumber = [notification object];
BOOL result = [newNumber boolValue];
if (result) {
if(timerRefresh)
{
if ([timerRefresh isValid])
[timerRefresh invalidate];
timerRefresh = nil;
}
}
else
{
if(timerRefresh)
{
if ([timerRefresh isValid])
[timerRefresh invalidate];
timerRefresh = nil;
}
if (!self.isConnectionAvailable) {
timerRefresh = [NSTimer timerWithTimeInterval:appDelegate.TimerInterval target:self selector:#selector(startAutoRefresh) userInfo:nil repeats:NO];
}
else if (self.isLivePresent||self.isUpcomingMatchToday) {
timerRefresh = [NSTimer timerWithTimeInterval:appDelegate.TimerInterval target:self selector:#selector(startAutoRefresh) userInfo:nil repeats:NO];
}
else {
timerRefresh = [NSTimer timerWithTimeInterval:LongRefresh target:self selector:#selector(startAutoRefresh) userInfo:nil repeats:NO];
}
NSRunLoop *runLoop = [NSRunLoop mainRunLoop];
[runLoop addTimer:timerRefresh forMode:NSDefaultRunLoopMode];
[runLoop run];
}
}
When the filtering process is on, i stop the parent timer. And when off i start it again.
Ok...So now the problem... When i do normal navigation on my pages, it works absolutely fine..like switching tabs, traversing between pages etc.
But if i use the filter process, somehow, it triggers my timer on the main page, and even when the view has disappeared, seems to kick off my timer event. I want to avoid that, but i just dont know how..
If anyone can genuinely help me, please do.
Thanks in advance.
There is some funny stuff going on in your code:
First and formemost, I am at least least 90% positively sure that you don't want to call [[NSRunLoop mainRunLoop] run] in any of your program's methods — do those methods even exit, or do you keep aggregating thread upon thread?
Secondly, your timer invalidations are all a bit strange:
There is no use in if (timerRefresh) if ([timerRefresh isValid]) [timerRefresh invalidate];; since in Objective C, messaging nil is perfectly fine. The result of such a message is always 0x0, so the first if is unnecessary and the second one evaluates to NO in that case, anyway.
Invalidating a timer means removing it from the runloop it was scheduled on. Hence, the second if is unnecessary, too — leaving you with just [timerRefresh invalidate];.
For -[NSTimer invalidate] to have an effect, it needs to be called on the thread the timer is scheduled on. From what I understood, this is not the case in all your methods. So you should use performSelectorOnMainThread:withObject:waitUntilDone: with the appropriate arguments instead.
There is no difference between [self performSelector:#selector(sendTeamNameClickToFunction:) withObject:sender] and simply [self sendTeamNameClickToFunction:sender]. Except that the latter is much easier to read ;-)
The if clauses in pauseAndResumeTimer: don't make an awful lot of sense, i.e. there's a lot of code duplication.
Here is said method in a tidied-up fashion and with the invalidation happening on the main thread:
-(void)pauseAndResumeTimer:(NSNotification *)notification
{
[timerRefresh performSelectorOnMainThread:#selector(invalidate) withObject:nil waitUntilDone:YES];
timerRefresh = nil;
NSNumber *result = [notification object];
if ([result boolValue]) return;
if ( !self.isConnectionAvailable || self.isLivePresent || self.isUpcomingMatchToday ) {
timerRefresh = [NSTimer timerWithTimeInterval:appDelegate.TimerInterval target:self selector:#selector(startAutoRefresh) userInfo:nil repeats:NO];
} else {
timerRefresh = [NSTimer timerWithTimeInterval:LongRefresh target:self selector:#selector(startAutoRefresh) userInfo:nil repeats:NO];
}
[[NSRunLoop mainRunLoop] addTimer:timerRefresh forMode:NSDefaultRunLoopMode];
}
I am aware of the many questions regarding this topic, as I myself have asked one previously however, my issue now seems to be more related to the threading part. I have the following 2 methods.
-(void) restartTimer {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc]init];
self.timer = [NSTimer scheduledTimerWithTimeInterval:1.
target:self
selector:#selector(dim)
userInfo:nil
repeats:YES];
time = 31;
NSLog(#"calling restart timer");
[self performSelectorOnMainThread:#selector(timerImageUpdate) withObject:nil waitUntilDone:NO];
[[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSDefaultRunLoopMode];
[[NSRunLoop currentRunLoop] run];
[pool drain];
}
-(void) resumeTimer {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc]init];
self.timer = [NSTimer scheduledTimerWithTimeInterval:1.
target:self
selector:#selector(dim)
userInfo:nil
repeats:YES];
NSLog(#"calling resume timer");
[[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSDefaultRunLoopMode];
[[NSRunLoop currentRunLoop] run];
[pool drain];
}
The restartTimer function is called when the game begins. This works fine and the timer fires the dim selector nicely. The problem occurs when the user clicks my skip button in quick succession. [skip] updates an MKMapView and in the delegate method mapdidfinishloading the follwing is called :
[NSThread detachNewThreadSelector:#selector(restartTimer) toTarget:self withObject:nil];
When this happens it seems several timers are created and thus my dim function is called far too often giving the appearance of the single timer running really fast? What is the best way to start and restart the timer using a secondary thread? Note this problem only seems to happen if the skip button is pressed repeatedly and quickly, while it works fine if just pressed now and again?
Anyone have any ideas? Many thanks
Jules
You can use a bool to know if you have a timer running or not. When you start the timer you set it to true, when you stop the timer you set it to false. When resume timer function is called you check this variable and if it's true you do not start a new timer.
Another solution would be to limit user interaction with the button. If the button is pressed you make it inactive for a time.
Is it possible to call my function in the background after certain interval programmatically in iPhone SDK development? I want to call one particular function in the background for certain time intervals(may be every 10 mins) during my app in on running..
Could you please share your ideas.
thanks.
Clave/
Easiest way is to schedule a NSTimer on the main threads run-loop. I suggest that the following code is implemented on your application delegate, and that you call setupTimer from applicationDidFinishLaunching:.
-(void)setupTimer;
{
NSTimer* timer = [NSTimer timerWithTimeInterval:10 * 60
target:self
selector:#selector(triggerTimer:)
userInfo:nil
repeats:YES];
[[NSRunLoop mainRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
}
-(void)triggerTimer:(NSTimer*)timer;
{
// Do your stuff
}
If your stuff here takes a long time, and you can not hold up the main thread then either call your stuff using:
[self performSelectorInBackground:#selector(myStuff) withObject:nil];
Or you could run the NSTimer on a background thread by with something like this (I am intentionally leaking the thread object):
-(void)startTimerThread;
{
NSThread* thread = [[NSThread alloc] initWithTarget:self
selector:#selector(setupTimerThread)
withObject:nil];
[thread start];
}
-(void)setupTimerThread;
{
NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
NSTimer* timer = [NSTimer timerWithTimeInterval:10 * 60
target:self
selector:#selector(triggerTimer:)
userInfo:nil
repeats:YES];
NSRunLoop* runLoop = [NSRunLoop currentRunLoop];
[runLoop addTimer:timer forModes:NSRunLoopCommonModes];
[runLoop run];
[pool release];
}
-(void)triggerTimer:(NSTimer*)timer;
{
// Do your stuff
}
You can have a timer, look into NSTimer for that that will fire off every 10 minutes, in order to make i t happen i n the background you have a few options ill name two.
First of note that any UI work should not be done in another thread since UIKit is not thread safe.
You can subclass NSThread and use it to do you process in the background
You can use NSObjects performSelectorInBackground method which basically creates a thread and executes the method. Heres a reference http://developer.apple.com/iphone/library/documentation/Cocoa/Reference/Foundation/Classes/NSObject_Class/Reference/Reference.html#//apple_ref/occ/instm/NSObject/performSelectorInBackground:withObject:
NSThread reference here http://developer.apple.com/iphone/library/documentation/Cocoa/Reference/Foundation/Classes/NSThread_Class/Reference/Reference.html
NSTimer reference here
http://developer.apple.com/iphone/library/documentation/Cocoa/Reference/Foundation/Classes/NSTimer_Class/Reference/NSTimer.html
To call the function on the main thread, use an NSTimer.
To call it on another thread, create an NSOperation and set up an NSOperationQueue to call it.