NSTimer from secondary NSThread doesn't work - ios

As per the documentation of Run Loop if there is any input source NSThread will be running otherwise it will go to sleep. I configured the timer same as provided under "Configuring Timer Sources" in above link but its not triggering. I am using below code.
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
[NSThread detachNewThreadSelector:#selector(testOnThread) toTarget:self withObject:nil];
}
- (void) testThread
{
NSLog(#"Test");
}
-(void)testOnThread
{
#autoreleasepool {
NSRunLoop* myRunLoop = [NSRunLoop currentRunLoop];
// Create and schedule the first timer.
NSDate* futureDate = [NSDate dateWithTimeIntervalSinceNow:1.0];
NSTimer* myTimer = [[NSTimer alloc] initWithFireDate:futureDate
interval:0.1
target:self
selector:#selector(testThread)
userInfo:nil
repeats:YES];
[myRunLoop addTimer:myTimer forMode:NSDefaultRunLoopMode];
}
}
Above code never prints "Test".
But timer is firing every time if I put [[NSRunLoop currentRunLoop] run]; at the end of -(void)testOnThread methods it works fine(Stackoverflow Question). My query is if we are already providing timer input Source to run loop than what is the need to explicitly start it using [[NSRunLoop currentRunLoop] run];

I'll let others answer the question why you have to run the runloop yourself. But I'd like to suggest an alternative:
If you want to run timer on background thread, using dispatch timer is easiest, IMHO, with no runloop required at all. Just define timer property:
#property (nonatomic, strong) dispatch_source_t timer;
And then schedule the timer to run on a custom queue:
dispatch_queue_t queue = dispatch_queue_create("com.domain.app.timer", 0);
self.timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
dispatch_source_set_timer(self.timer, dispatch_walltime(NULL, 0), 20ull * NSEC_PER_SEC, 1ull * NSEC_PER_SEC);
dispatch_source_set_event_handler(timer, ^{
// code to be performed periodically on background thread here
});
dispatch_resume(self.timer);

Related

NSTimer inside global queue is not called when the app is in the background

I want to run a selector using NSTimer which contains some network calls and some other tasks. I want to do that on global queue.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSTimer * myTimer=[[NSTimer alloc]init];
myTimer = [NSTimer timerWithTimeInterval:10*60 target:self selector:#selector(syncGroupAutomatically) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:myTimer forMode:NSRunLoopCommonModes];
[[NSRunLoop currentRunLoop] run];
});
-(void)syncGroupAutomatically
{
NSLog(#"Some Network calls and some other things\n");
}
If I run this code, it works fine when app is in foreground, but as soon as I press the home button it stops calling the syncGroupAutomatically method.
If anybody having any idea how to run this NSTimer even when app is in the background. Please help me.

NSTimer not calling Method [duplicate]

This question already has answers here:
NSTimer timerWithTimeInterval: not working
(4 answers)
Closed 9 years ago.
I have an NSTimer that should call a method every second, but it doesn't seem to be calling it at all.
This is how I declare the timer:
[NSTimer timerWithTimeInterval:1.0 target:self selector:#selector(fadeManager:) userInfo:nil repeats:YES];
This is the method it calls:
-(void) fadeManager: (NSTimer*) timer{
NSLog(#"IT'S WORKING!");
self.playHead += .1; // keeps track of where they are in full track
if (self.playHead >= self.wait && ![player isPlaying]) { // checks if wait is over
[player play];
}
if (self.playHead >= self.duration + self.startTime && [player isPlaying]) { // checks if full duration is over
[player pause];
[self reset];
}
int fadeOutArea = self.startTime + self.duration - self.fadeOut;
int fadeInArea = self.startTime + self.fadeIn;
if (self.playHead <= fadeInArea && [player volume] < relativeVolume) { // checks if fadingIn.
[self fadeInIncriment];
}
if (self.playHead >= fadeOutArea && [player volume] > 0) {
[self fadeOutIncriment];
}
}
The code was not working so I put the NSLog in as well as a break point. It seems that it is never being called. Why is this? Does it matter that I declared the method in the .m file like this:
#import <AVFoundation/AVFoundation.h>
#interface CueMusic ()
- (void) delayFadeOut: (AVAudioPlayer*) dFade;
- (void) fadeInIncriment;
- (void) fadeOutIncriment;
- (void) fadeManager: (NSTimer*) timer; // <--------
- (void) start;
#end
#implementation CueMusic
.......
Either use
NSTimer *timer = [NSTimer timerWithTimeInterval:1.0 target:self selector:#selector(fadeManager:) userInfo:nil repeats:YES];
[[NSRunLoop mainRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
or
//schedules the timer
[NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(fadeManager:) userInfo:nil repeats:YES];
From the docs Scheduling Timers in Run Loops
Use the scheduledTimerWithTimeInterval:invocation:repeats: or scheduledTimerWithTimeInterval:target:selector:userInfo:repeats: class method to create the timer and schedule it on the current run loop in the default mode.
Use the timerWithTimeInterval:invocation:repeats: or timerWithTimeInterval:target:selector:userInfo:repeats: class method to create the timer object without scheduling it on a run loop. (After creating it, you must add the timer to a run loop manually by calling the addTimer:forMode: method of the corresponding NSRunLoop object.)
Swift Code
Either
let timer: NSTimer = NSTimer(timeInterval: 1.0, target: self, selector: "fadeManager:", userInfo: nil, repeats: true)
NSRunLoop.mainRunLoop().addTimer(timer, forMode: NSDefaultRunLoopMode)
Or
//schedules the timer
NSTimer.scheduledTimerWithTimeInterval(1.0, target: self, selector: "fadeManager:", userInfo: nil, repeats: true)
Your problem is that when using timerWithTimeInterval:target:selector:userInfo:repeats:, the resulting timer does not automatically get added to the run loop. I would recommend using scheduledTimerWithTimeInterval:target:selector:userInfo:repeats: instead, which performs this step for you.
If you prefer to use timerWithTimeInterval:target:selector:userInfo:repeats: then you need to manually add the timer to the current run loop. To do this, call NSRunLoop's addTimer:forMode: method. Documentation
[[NSRunLoop currentRunLoop] addTimer:tTimer forMode: NSDefaultRunLoopMode];
You need to fire the timer.
You can do it by adding it to a thread:
NSTimer *timer = [NSTimer timerWithTimeInterval:1.0 target:self selector:#selector(fadeManager:) userInfo:nil repeats:YES];
[[NSRunLoop mainRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
or calling
[timer fire];
You can use:
[[NSRunLoop mainRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];

Will runloop block GCD?

I add a timer on Thread A and start the runloop, after that I dispatch_async a Method_Foo on Thread A, and Method_Foo doesn't run. What I guessing is that the dispatch methods will be blocked by the runLoop on that thread but I not sure. Is that true or am I missing something?
More details:
In the network layer I use delegate to notify network status, the _delegateQueue is from an global instance, let's say Thread_A. And this socket:didConnectToHost:port: method is running in a Thread_B.
- (void)socket:(GCDAsyncSocket *)sock didConnectToHost:(NSString *)host port:(uint16_t)port
{
// called when connected to server
...
dispatch_async(_delegateQueue, ^{
// this runs in Thread_A to notify network status
[theDelegate socketConnection:self didChangeStatus:YES];
});
...
}
Then in the upper layer there's a class run the _startReconnectingProcess method after disconnected with the server in Thread_A. To make the timer to work, I start the runLoop in Thread_A.
// runs in Thread_A to start reconnection process
- (void)_startReconnectingProcess
{
if (self.reconnectionTimer || _forceDisconnection == YES) return;
self.reconnectionTimer = [NSTimer scheduledTimerWithTimeInterval:kReconnectionTimeInterval target:self selector:#selector(_reconnect) userInfo:nil repeats:YES];
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
[runLoop addTimer:self.reconnectionTimer forMode:NSDefaultRunLoopMode];
// run the runLoop in Thread_A
[runLoop run];
// after the runLoop start, the dispatch_async method in the network layer doesn't work
self.reconnectionCount = 0;
}
So what happens is that when the timer starts, the method [theDelegate socketConnection:self didChangeStatus:YES] never be called, which should.

NSTimer requiring me to add it to a runloop

I am wondering if someone can explain why dispatching back to the main queue and creating a repeating NSTimer I am having to add it to RUN LOOP for it too fire? Even when using performselectorOnMainThread I still have to add it to a RUN LOOP to get it to fire.
Below is an example of my question:
#define queue dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
#define mainqueue dispatch_get_main_queue()
- (void)someMethodBeginCalled
{
dispatch_async(queue, ^{
int x = 0;
dispatch_async(mainqueue, ^(void){
if([_delegate respondsToSelector:#selector(complete:)])
[_delegate complete:nil];
});
});
}
- (void)compelete:(id)object
{
[self startTimer];
//[self performSelectorOnMainThread:#selector(startTimer) withObject:nil waitUntilDone:NO];
}
- (void)startTimer
{
NSTimer timer = [NSTimer timerWithTimeInterval:3 target:self selector:#selector(callsomethingelse) userInfo:nil repeats:YES];
//NSDefaultRunLoopMode
[[NSRunLoop currentRunLoop] addTimer:_busTimer forMode:NSRunLoopCommonModes];
}
EDIT:
I believe I worded this question very poorly. I would like to know why [[NSRunLoop currentRunLoop] addTimer:_busTimer forMode:NSRunLoopCommonModes]; is necessary in startTimer if I call someMethodBeginCalled. If I don't include that line, the timer doesn't fire.
If I call startTimer from viewDidLoad for example, I can remove the NSRunLoop line and the timer will fire every 60 seconds.
And here's how to add an NSTimer to a runloop:
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
[runLoop addTimer:timer forMode:NSDefaultRunLoopMode];
You could always use this method instead:
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:3 target:self selector:#selector(getBusLocation) userInfo:nil repeats:YES];
This will save you a line, as it will add it to the run loop automatically.
Because, as the docs say:
Timers work in conjunction with run loops. To use a timer effectively,
you should be aware of how run loops operate—see NSRunLoop and
Threading Programming Guide. Note in particular that run loops retain
their timers, so you can release a timer after you have added it to a
run loop.
It is a design decision that Apple made when they wrote the code for NSTimer (and I'm sure they had good reason to do so) and there is nothing we can do to get around it. Is it really that burdensome?
Like #sosborn said, NSTimers depend on NSRunLoops, and since GCD queues create threads that don't have run loops, NSTimer doesn't play well with GCD.
Check out this other StackOverflow question on the matter: Is it safe to schedule and invalidate NSTimers on a GCD serial queue?
To solve that problem, I implemented MSWeakTimer: https://github.com/mindsnacks/MSWeakTimer (and had the implementation checked by a libdispatch engineer at the last WWDC!)
Timer method won't be called since GCD queues create threads that don't have run loops
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
[NSTimer scheduledTimerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {
NSLog(#"Timer method from GCD main queue");
}];
});
However when dispatched on main queue the timer method will be called as it will get added to main threads run loop.
dispatch_async(dispatch_get_main_queue(), ^{
[NSTimer scheduledTimerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {
NSLog(#"Timer method from GCD main queue");
}];
});
Adding the timer to the runloop didn't work in my case. I had to create the timer on the main thread. I was doing this thread creation in a MultipeerConnectivity delegate.
dispatch_async(dispatch_get_main_queue(), ^{
self.timer = [NSTimer scheduledTimerWithTimeInterval:self.interval invocation: self.invocation repeats:YES];
});

The selector of my NSTimer does not run. Why?

My code is:
-(void) timerRun{...}
-(void) createTimer
{
NSTimer *timer;
timer = [NSTimer timerWithTimeInterval:1.0
target:self
selector:#selector(timerRun)
userInfo:nil
repeats:YES];
}
viewDidLoad
{
[NSThread detachNewThreadSelector:#selector(createTimmer)
toTarget:self withObject:nil];
...
}
When I debug, the method createTimer runs ok, but the method does timerRun not run?
Just creating a timer doesn't start it running. You need to both create it and schedule it.
You're actually going to have to do slightly more work than that if you want it to run on a background thread. NSTimers attach to NSRunloops, which are the Cocoa form of an event loop. Each NSThread inherently has a a run loop but you have to tell it to run explicitly.
A run loop with a timer attached can run itself indefinitely but you probably don't want it to because it won't be managing autorelease pools for you.
So, in summary, you probably want to (i) create the timer; (ii) attach it to that thread's run loop; (iii) enter a loop that creates an autorelease pool, runs the run loop for a bit and then drains the autorelease pool.
Code will probably look like:
// create timer
timer = [NSTimer timerWithTimeInterval:1.0
target:self
selector:#selector(timerRun)
userInfo:nil
repeats:YES];
// attach the timer to this thread's run loop
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
// pump the run loop until someone tells us to stop
while(!someQuitCondition)
{
// create a autorelease pool
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
// allow the run loop to run for, arbitrarily, 2 seconds
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:2.0]];
// drain the pool
[pool drain];
}
// clean up after the timer
[timer invalidate];
You have to schedule a timer for it to run. They get attached to a run loop, which in turn updates the timer as necessary.
You can either change createTimer to
[NSTimer scheduledTimerWithTimeInterval:1.0
target:self
selector:#selector(timerRun)
userInfo:nil
repeats:YES];
or add
[[NSRunLoop currentRunLoop] addTimer:timer forModes:NSRunLoopCommonModes];
The method signature that you use in scheduledTimerWithTimeInterval:target:selector:userInfo:repeats: must have an argument for the NSTimer as it passes itself as an argument.
You should change your message signature to:
(void)timerRun:(NSTimer *)timer;
You don't need to do anything with the argument, but it should be there. Also in createTimer the selector will become #selector(timerRun:) as it now accepts an argument:
timer = [NSTimer timerWithTimeInterval:1.0
target:self
selector:#selector(timerRun:)
userInfo:nil
repeats:YES];

Resources