I have some web service data in my app that needs to be updated every 3 minutes.
I had tried out a few approaches but got a really good piece of advise in here last week, I should not build a new thread every 3 minutes and then subsequently try and dealloc and synchronize all the different parts so that I avoided memory bug. Instead I should have a "worker thread" that was always running, but only did actual work when I asked it too (every 3 minutes).
As my small POC works now, I spawn a new thread in the applicationDidFinishLaunching
method. I do this like so:
[NSThread detachNewThreadSelector:#selector(updateModel) toTarget:self withObject:nil];
- (void) updateModel {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
BackgroundUpdate *update = [[BackgroundUpdate alloc] initWithTimerInterval:180];
[update release];
[pool release];
}
Ok, this inits the "BackgroundUpdate" object with the update interval in seconds. Inside the updater it is simply like this for now:
#implementation BackgroundUpdate
- (id) initWithTimerInterval:(NSInteger) secondsBetweenUpdates {
if(self = [super init]) {
[NSTimer scheduledTimerWithTimeInterval:secondsBetweenUpdates
target:self
selector:#selector(testIfUpdateNeeded)
userInfo:nil
repeats:YES];
}
return self;
}
- (void) testIfUpdateNeeded {
NSLog(#"Im contemplating an update...");
}
I have never used threads like this before. I has always been "setup autoReleasePool, do work, get your autoReleasePool drained, and goodbye".
My problem is that as soon as the initWithTimerInterval has run, the NSThread is done, it therefore returns to the updateModel method and has its pool drained. I guess it has to do with the NSTimer having it's own thread/runloop? I would like for the thread to keep having the testIfUpdateNeeded method run every 3 minutes.
So how will I keep this NSThread alive for the entire duration of my app?
Thank You for any help/advice given:)
You're close. All you need to do now is start the run loop running so the thread doesn't exit and the timer runs. After your call to initWithTimerInterval:, just call
[[NSRunLoop currentRunLoop] run];
The thread will run its run loop indefinitely and your timer will work.
It sounds like you might want an NSOperation instead of an old fashion thread. You could active the operation via a universal timer then it would execute on its own thread and then clean up its own memory when done.
Related
I keep having troubles with my NSTimers and background selectors. It is driving me nuts and takes a very long time to try out each tweak. To preserve my sanity, and the sanity of future generations of cocoa programmers, I'm asking this question:
Is there an absolutely 100% sure way to have a scheduled, long-term timer fire at a later point in time, regardless of whether it was called from a background thread, main thread, etc?
It seems that I keep having to solve the same problem over and over again for the majority of my classes that use NSTimers. they work during short-term testing, let's say I set the timer to fire through a background thread to fire in 10 seconds. It works, because there's still a run loop running. But once I change the fire time to what I really want, like 15-30 minutes, there's dead silence. The run loop is gone and I don't know how to handle such a case. Nothing happens, and I discover such bugs a few days later, once I've already forgotten which timer would be responsible for that.
Currently I'm doing some really, really ugly dance with selectors, for example here's a test method(It seems to work for 10 minute timers):
//this is a test method to simulate a background task requesting a timer
[self performSelectorInBackground:#selector(backgroundReminderLongTermTest:) withObject:nil];
//this is a method similar to the one that the background thread would be trying to invoke
-(void)backgroundReminderLongTermTest:(id)sender
{
[self performSelectorOnMainThread:#selector(backgroundReminderFromMainThread:) withObject:nil waitUntilDone:NO];
}
//this is a wrapper for the background method, I want the timer to be added to a thread with a run loop already established and running
-(void)backgroundReminderFromMainThread:(id)sender
{
[playTimers addObject:[NSTimer scheduledTimerWithTimeInterval:1800 target:self selector:#selector(start:) userInfo:nil repeats:NO]];
}
I like the convenience of not having to worry about creating a fire date object with the scheduled timers, but should I just forget about them and use timers with specific fire dates? It seems that the scheduledTimer works well for short term tasks, when the run loop is already present, but I simply cannot see this kind of bugs during the app's execution. At one point, it seems that the timers are firing normally, but at a later point they stop firing completely.
Thank you for any help or clarification. I'm looking for a method that schedules timers without having to worry about whether or not a run loop is present every time I need to schedule a timer. I want to be sure that as long as the app is running, my timers, scheduled through this method would fire at predictable points in the future .
One of the myriad issues with NSTimers is their run-loop dependency. Every thread has a single run loop. If you schedule a timer on a background thread, it will be scheduled on that thread's run loop. If that thread is short lived, which background threads often are, that timer will quietly die with it.
The solution is to guarantee the timer is run on a thread that will be alive when the timer fires. The best way to do these dedicated background timers in my experience is to not use NSTimer at all, and go for GCD timers instead. Better men than I have coded up GCD powered timers. I personally prefer Mike Ash's article and implementation, which comes with an explanation.
Use local notification instead.
For as long as you depend on using scheduledTimerWithTimeInterval:... you cannot achieve what you want:
The timer will always be tied to the run-loop of the calling thread.
If there is no run-loop associated with that thread by the time of that message's invocation, there surely is one when the method returns as -[NSRunLoop currentRunLoop] creates a run-loop if necessary.
What you can do, if you don't like the other APIs for creation of a timer, is providing a category on NSTimer, which takes care of all the scheduling and so forth and that you can reuse in other projects.
Here is an example of what such a category might look like:
#pragma mark - setting up a timer:
+ (NSTimer *)yourPrefix_mainLoopScheduledTimerWithTimeInterval:(NSTimeInterval)interval target:(id)target selector:(SEL)selector userInfo:(id)userInfo repeats:(BOOL)shouldRepeat
{
NSTimer *timer = [self yourPrefix_timerWithTimeInterval:interval target:target selector:selector userInfo:userInfo repeats:shouldRepeat];
void (^scheduler)() = ^{
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
};
if ([NSThread isMainThread]) {
scheduler();
} else {
// you should really be able to rely on the fact, that the timer is ready to roll, when this method returns
dispatch_sync(dispatch_get_main_queue(), scheduler);
}
return timer;
}
// this is just a convenience for the times where you actually want an _unscheduled_ timer
+ (NSTimer *)yourPrefix_timerWithTimeInterval:(NSTimeInterval)interval target:(id)target selector:(SEL)selector userInfo:(id)userInfo repeats:(BOOL)shouldRepeat
{
NSDate *fireDate = [NSDate dateWithTimeIntervalSinceNow:interval];
NSTimer *timer = [[self alloc] initWithFireDate:fireDate interval:interval target:target selector:selector userInfo:userInfo repeats:shouldRepeat];
return [timer autorelease];
}
#pragma mark - tearing it down:
- (void)yourPrefix_invalidateMainLoopTimer
{
[self yourPrefix_invalidateMainLoopTimerAsynchronous:NO];
}
- (void)yourPrefix_invalidateMainLoopTimerAsynchronous:(BOOL)returnsImmediately
{
void (^invalidator)() = ^{
[self invalidate];
};
dispatch_queue_t mainQueue = dispatch_get_main_queue();
if (returnsImmediately) {
dispatch_async(mainQueue, invalidator);
return;
}
if (![NSThread isMainThread]) {
dispatch_sync(mainQueue, invalidator);
return;
}
invalidator();
}
Note the thread checks before using dispatch_sync because...
dispatch_sync
Discussion
[…] Calling this function and targeting the current queue results in deadlock.
(from The GCD Reference — emphasis mine)
Is considered thread-safe to call setFireDate: from another thread than the one in which the timer is scheduled? I mean, I detach this function in a new thread:
-(void)CFRunLoopTest {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
timer = [NSTimer timerWithTimeInterval:1 target:self selector:#selector(timerFireMethod:) userInfo:nil repeats:YES];
runLoop = CFRunLoopGetCurrent();
CFRunLoopAddTimer(runLoop, (CFRunLoopTimerRef)timer, kCFRunLoopCommonModes);
CFRunLoopRun();
[pool drain];
}
May I call [timer setFireDate:] from the main thread? I did not found anything in documentation that forbids it...
A note from the NSTimer reference for setFireDate: method says
You could potentially call this method
on a non-repeating timer that had not
yet fired, although you should always
do so from the thread to which the
timer is attached to avoid potential
race conditions.
Also see if the following
Discussion helps.
Why not run the timer on the main thread? I don't understand why you would need to run it in a separate thread. You could always have the timerFireMethod: spawn a new thread if it consumes a lot of time, Just run the appropriate method with performSelectorInBackground:withObject:.
EDIT: So the documentation actually says that it isn't thread safe to call[timer setFireDate:] from another thread. However, my advice is still valid.
While I usually make use of NSOperation for having a thread do a short task/operation, I'd like to have a long-living dedicated thread that is always available to process certain operations. For this, I allocate a new NSThread and use the initWithTarget method:
- (id)initWithTarget:(id)target selector:(SEL)selector object:(id)argument
My understanding is the selector passed in should be the thread's main method that is responsible for starting the runloop. What is the proper code to have in there?
Is a while loop like this appropriate...
(void)newThreadMainMethod {
while(1) {
[[NSThread currentThread] run];
}
}
or is there a more efficient way to do it so that the thread doesn't take up resources with a endless loop? I was thinking along the lines of having a timer wake up every 0.5 seconds and call run on the thread in case something new is available to work on. I would appreciate your input.
Thanks.
[[NSRunLoop currentRunLoop] run] is what you want, but it will carry on forever so you only need to call it once (not in a loop). You can either:
do that, terminating the thread from within the run loop when/if appropriate
use [[NSRunLoop currentRunLoop] runUntilDate: [NSDate dateWithTimeIntervalSinceNow: JIFFY_TIME], and poll for an exit condition in a while() loop like you've written.
I have a "sync" task that relies on several "sub-tasks", which include asynchronous network operations, but which all require access to a single NSManagedObjectContext. Due to the threading requirements of NSManagedObjectContexts, I need every one of these sub-tasks to execute on the same thread. Due to the amount of processing being done in some of these tasks, I need them to be on a background thread.
At the moment, I'm launching a new thread by doing this in my singleton SyncEngine object's -init method:
[self performSelectorInBackground:#selector(initializeSyncThread) withObject:nil];
The -initializeSyncThread method looks like this:
- (void)initializeSyncThread
{
self.syncThread = [NSThread currentThread];
self.managedObjectContext = [(MyAppDelegate *)[UIApplication sharedApplication].delegate createManagedObjectContext];
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
[runLoop run];
}
Is this the correct way to start up the NSRunLoop for this thread? Is there a better way to do it? The run loop only needs to handle 'performSelector' sources, and it (and its thread) should be around for the lifetime of the process.
When it comes to setting up an NSAutoreleasePool, should I do this by using Run Loop Observers to create the autorelease pool and drain it after every run-through?
I'm not 100% sure this is the most efficient way to solve your problem, but I'm doing like this...
Since the asynchronous network operations can take different time to complete (or to timeout) I keep track of them with an instance variable (in this case a BOOL called callComplete)
When the code for firing of the network reqeust is started, and my NSURLConnection has gone away and doing it's magic, I wait for the call to be complete like this.
while(!callComplete && [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]){
// Here you can perform other checks, like making sure the method isn't running forever (with a timeout variable)
// Just set callComplete to YES when done
}
Regarding the NSAutoreleasePool, I simply create one on the start of my selector that is run in the background. Then after the Run Loop is done (and my calls are complete) i release as usual before returning.
I want to create an appilication in iPhone in which I want to use NSThread. I have created one thread using
[NSThread detachNewThreadSelector:#selector(doThread:)
toTarget:self
withObject:nil];
I want that my one thread will handle all the touches and other user interaction and the second thread handle the NSTimer. So, In doThread() I have allocate NSTimer like,
-(void) doThread:(NSString *)poststring {
NSLog(#"create thread:");
[lock lock];
T1 = [NSTimer scheduledTimerWithTimeInterval:(5)
target : self
selector:#selector(onTimer)
userInfo : nil
repeats : YES];
NSLog(#"after timer");
usleep(1);
[lock unlock];
}
In onTImer,
-(void)onTimer
{
NSLog(#"in timer");
}
Now I can't able to call the onTimer method of NSTimer. But I can see the "after timer" printed in the log.Is that anything that I can't use the NSTimer within the thread?
This is also I can get while execution.
NSAutoreleaseNoPool(): Object 0xd15880 of class __NSCFDate autoreleased with no pool in place - just leaking
Stack: (0x305a2e6f 0x30504682 0x30525acf 0x27b5 0x3050a79d 0x3050a338 0x926ae155 0x926ae012)
Please help me for that.
Thank you.
NSTimer schedules its time events on the current NSRunLoop--your thread doesn't start one.
If all you are trying to do is run something after a certain amount of time, use -[NSObject performSelector:withObject:afterDelay:]:
[self performSelector:#selector(onTimer) withObject:nil afterDelay:5.0f];
If you are trying to actually do work in the background, +[NSThread detachNewThreadSelector:toTarget:withObject:] will work as expected but you shouldn't run timer events in the background without an NSRunLoop. Also, you will need to wrap your code in an autorelease pool:
- (void)doThread:(NSString *)poststring
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
// Your code goes in here
[pool drain];
}
I'm not sure I understand your question about the onTimer method. Can you restate it?
As for:
NSAutoreleaseNoPool(): Object 0xd15880 of class __NSCFDate autoreleased with no pool in place - just leaking
A few things can cause this:
If you're not delegating or subclassing an UIApplication object, you won't have an autorelease pool in place and would have to create one on your own. However, the right answer in that case is just to be sure you're using UIApplication correctly.
In this case however, since you're detaching the thread, that's likely the cause for the error. Detached threads don't have autorelease pools, so you'd have to create your own.
See the documentation:
Autorelease Pools