I am trying to timeout my NSOperation with a NSTimer but my timer is not getting fired. Please see below the code I have written inside my class which is sub classing NSOperation.
- (void)start {
// Start the timer for Time out before the ping activity starts
self.timeOutTriggerTimmer = [NSTimer scheduledTimerWithTimeInterval:10.0 target:self selector:#selector(cancelTheOperation) userInfo:nil repeats:NO];
[[NSRunLoop currentRunLoop] addTimer:self.timeOutTriggerTimmer forMode:NSDefaultRunLoopMode];
// Check for cancellation
if ([self isCancelled]) {
[self completeOperation];
return;
}
// Executing
[self willChangeValueForKey:#"isExecuting"];
executing = YES;
[self didChangeValueForKey:#"isExecuting"];
// Begin
[self beginOperation];
}
It's easiest to just add the timer to the main run loop, not the current run loop:
[[NSRunLoop mainRunLoop] addTimer:self.timeOutTriggerTimmer forMode:NSDefaultRunLoopMode];
Alternatively, you can keep your timer as it is (scheduled on the current run loop), but then you have to keep the runloop alive, perhaps adding something like the following to the end of the start method (note, Apple recommends this rather than the run method):
while ([self isExecuting] && [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]);
But this (like the run method you contemplated) effectively keep the start method running until execution is done (making what appears to be a concurrent operation to behave more like a non-concurrent operation).
I suspect you're doing this already, but just in case, make sure to invalidate your timer when you complete your operation, or else the timer will retain the operation until the timer fires, unnecessarily delaying the freeing of the operation's resources (and calling the cancelTheOperation even though the operation may well already be done).
I found the issue. I need to put the below statement to have it executed.
[[NSRunLoop currentRunLoop] run];
Related
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.
Im writing a little command line tool that use AFHTTPNetworkng to pull some data from a server and display it. However because AFHTTPNetwotking uses an asynchronous call back, the application exits prior to getting the response from the server. I there for added
NSRunLoop *run = [NSRunLoop currentRunLoop];
_timer = [NSTimer timerWithTimeInterval:0.5 target:self selector:#selector(check) userInfo:nil repeats:YES];
[run addTimer:_timer forMode:NSDefaultRunLoopMode];
[run runUntilDate:[NSDate distantFuture]];
this timer calls a check method that checks to see if a BOOL has been set to yes (This happens in the call back). If it has it invalidates the timer.
- (void)check{
NSLog(#"checking");
if (test == YES) {
[_timer invalidate];
}
return;
}
This code works fine but now the app does not terminate, suggesting that the runloop is still running. I could use exit() to kill the app but this does not seem acceptable as this may cause a memory leak.
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.
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];
});
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];