I'm having trouble with a simple threading example. I know my methods aren't complete, but as of now, I want to press the start button and have that fire off a thread that increments a counter every second. I know it's connected in IB correctly since the NSLog tells me it gets to my timerThread method. But then it immediately jumps back to the initial myThread, without ever reaching the updateDisplay method and releases the pool, which is why I am guessing my program doesn't actually increment a counter. I thought I would then put it in a sleep interval or something, but in the end I think I am missing the correct way to achieve this. Any thoughts would be great. Thanks!
#implementation MainController
-(id)initWithLabel:(UILabel *)label {
if (self = [super init]) {
countLabel = label;
[countLabel retain];
}
return self;
}
-(int)count {
return count;
}
-(void)setCount:(int) value {
count = value;
}
-(void)updateDisplay:(NSTimer *)timer {
NSLog(#"%s", __FUNCTION__);
countLabel.text = [NSString stringWithFormat:#"%i", count];
count++;
}
-(void)timerThread {
NSLog(#"%s", __FUNCTION__);
[NSTimer timerWithTimeInterval:1.0
target:self
selector:#selector(updateDisplay:)
userInfo:nil
repeats:YES];
//NSNumber *threadID = [NSNumber numberWithInt:(int)threadID];
// threadLabel = [NSString stringWithFormat:#"%#", threadID];
}
-(void)myThread {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
//NSNumber *threadID = [NSNumber numberWithInt:(int)threadID];
[self performSelectorOnMainThread:#selector(timerThread)
withObject:nil
waitUntilDone:NO];
// NSLog(#"threadID in myThread: %#", threadID);
[pool release];
}
-(void)startThread {
// threadIndex = 0;
// numThreads = 0;
// NSNumber *threadID = [NSNumber numberWithInt:threadIndex++];
[self performSelectorInBackground:#selector(myThread) withObject:nil];
// NSLog(#"%i", threadIndex);
numThreads++;
}
-(void)myThreadStop {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
[NSThread exit];
[self performSelectorOnMainThread:#selector(updateDisplay)
withObject:nil
waitUntilDone:NO];
[pool release];
}
-(void)stopThread {
[self performSelectorInBackground:#selector(myThreadStop) withObject:nil];
}
-(void) dealloc {
[countLabel release];
[super dealloc];
}
#end
The short answer is that you did not schedule the timer.
iOS (and others) use run loops. Each thread may have a run loop, and the main UI thread has a run loop set up for you. Simplistically, a run loop keeps a queue of things to do and either does them in an ordered fashion or blocks until there is something to do.
Anything that you do with the active UI, like setting the text of a UILabel, must be done on the main thread. In your case, you have set up (but not scheduled) a timer on the main thread to update the timer. Scheduling a timer just means adding it to the run loop.
If you had a lengthy task to perform that would update the UI at the end, you could use performSelectorInBackground to start the lengthy task and performSelectorOnMainThread when the task finishes to update UI.
If you have a short periodic task, such as updating a clock or counter UI, you can just create an NSTimer on the same thread you want the timer to fire on. When creating the timer, use a scheduledTimerWithTimeInterval variant so it will start firing automatically.
When you create a repeating timer, you must keep a reference to it so you can invalidate the timer before the target is released. At the latest, in dealloc you should invalidate the timer.
Instead of calling startThread, turn timerThread into startTimer.
-(void) startTimer {
timerMember = [[NSTimer
scheduledTimerWithTimeInterval:1.0
target:self
selector:#selector(updateDisplay:)
userInfo:nil
repeats:YES] retain];
}
-(void) dealloc {
[timerMember invalidate];
[timerMember release];
...
}
Related
I have an NSOperation running on a background thread, and I want to change a property of it, a float named runSpeed, upon getting a memory warning on the main thread. The next time the background thread hits runSpeed though, it appears to be the same value it was previously.
How can I make sure it changes in the NSOperation thread when i change it on the main thread?
EDIT:
Didn't post code originally because I assumed I was thinking about it wrong. Here's my code:
AppDelegate
-(void)applicationDidReceiveMemoryWarning:(UIApplication *)application {
NSLog(#"memory warning");
if (dataUpdateIsActive) {
NSLog(#"data update is active");
dataUpdate.runSpeed = 10;
[NSTimer scheduledTimerWithTimeInterval:10
target:self
selector:#selector(resetRunSpeed)
userInfo:nil
repeats:NO];
}
}
Then in a method:
NSOperationQueue *operationQueue = [[NSOperationQueue alloc] init];
dataUpdate = [[JDataUpdate alloc] init];
[dataUpdate setOldPSC:[oldContext persistentStoreCoordinator]];
[dataUpdate setCurrentPSC:[newContext persistentStoreCoordinator]];
[dataUpdate setRunSpeed:0.5];
[dataUpdate setEntriesToCreate:250];
[dataUpdate setSaveFrequency:10];
dataUpdateIsActive = YES;
[operationQueue addOperation:dataUpdate];
Both of those NSLogs get triggered.
NSOperation
- (void)main
{
for (NSInteger index = 0; index < [self entriesToCreate]; ++index) {
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:[self runSpeed]]];
}
}
I have to loop in particluar method which do some task. I have used the method performselectoronmainthread wait until done method. Its working fine if i call it for once. But It fails when I call it in for loop. ie
this is the code:
for (int i=1;i<=3;i++) {
ip=i;
[self performSelectorOnMainThread:#selector(createThread:) withObject:ip waitUntilDone:YES];
}
-(void)createThread:(NSString*)ipIs
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSLog(#"Ip address is :%#",ipIs);
[SimplePingHelper ping:ipIs target:self sel:#selector(Result:)];
[pool release];
}
- (void)Result:(NSNumber*)success {
//do some stuff
}
The problem is that this code works fine when I run this loop once and It calls Result method. But it execution path changes when I try to use loop in performSelectorOnMainThread to pass different variables into it.Then It doesnt call result method.
I am looping because I want to run these same methods on different variables but task to be performed would be same.I am also using waituntil done:YES value..but still it is not working
Any idea ?
try creating other thread
myThread = [[NSThread alloc] initWithTarget:self selector:#selector(setupTimerThread) object:nil];
[myThread start];
-(void)setupTimerThread
{
NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
timer2 = [NSTimer timerWithTimeInterval:0.04
target:self
selector:#selector(triggerTimer:)
userInfo:nil
repeats:YES];
NSRunLoop* runLoop = [NSRunLoop currentRunLoop];
[runLoop addTimer:timer2 forMode:NSDefaultRunLoopMode];
[runLoop run];
[pool release];
}
-(void)triggerTimer:
{
//this method will be called in loop set loop interval by setting the timers time duration in above method
}
EDIT :
int ip=0;
-(void)triggerTimer:
{
ip++; // till ip<=3
[self createThread:ip];
}
I am using NSThread along with NSTimer.
My code is like this
-(void) checkForRecentAlarm
{
if ([self.alarmThread isFinished])
{
[self.alarmThread cancel];
}
self.alarmThread = [[NSThread alloc] initWithTarget:self selector:#selector(startTimerForRecentAlarm) object:nil];
[self.alarmThread start];
//[NSThread detachNewThreadSelector:#selector(startTimerForRecentAlarm) toTarget:self withObject:nil];
}
-(void)startTimerForRecentAlarm
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc]init];
self.recentAlarmTime = [NSDate date];
self.dbObject = [[RADataBaseModelManager alloc] init];
self.recentAlarmTime = [self.dbObject getMostRecentAlarmTimeFromDB];
if (self.recentAlarmTime) {
NSTimeInterval timeIntervalToAlarm = [self.recentAlarmTime timeIntervalSinceNow];
NSRunLoop* runLoop = [NSRunLoop currentRunLoop];
//Fire timer every second to updated countdown and date/time
self.RATimer = [NSTimer scheduledTimerWithTimeInterval:timeIntervalToAlarm target:self selector:#selector(timerFireMethod:) userInfo:nil repeats:NO];
[runLoop run];
}
[pool release];
}
- (void)timerFireMethod:(NSTimer*)theTimer
{
[self.RATimer invalidate];
[theTimer invalidate];
self.RATimer = NULL;
theTimer = NULL;
[self playAlarm];
UIAlertView *alarmAlert = [[UIAlertView alloc] initWithTitle:#"Alarm" message:#"" delegate:self cancelButtonTitle:#"Close" otherButtonTitles:#"Snooze", nil];
[alarmAlert show];
[alarmAlert release];
alarmAlert = nil;
}
Now the problem is, my alertbox comes twice for one call in the
startTimerForRecentAlarm
method. So that the alert comes consequently twice and my view get stuck.
What will be problem here?
I am trying to implement an alarm with multiple alarm option using a single NSTimer.
Please help.
When I debug this, I can find many simultaneous threads are running on same code(UIAlertView).
I can't see any obvious reason why that would be called twice, but it does seem like an overly complex way of doing what you need to do.
Have you thought about using local notifications?
If you don't want to do that, you could refactor your code so it works like this:
1. Add a new event
2. If there's no timer or the time to the event is shorter than the time on the timer, then set the timer for this event.
3. When a timer fires, check for the next event and set a timer for that event (if there is one).
This does seem really complex. My general observation is that if you get two timer firings it's because you have two timers for some reason.
If you have multiple threads doing UIAlertView, you have another problem, because you can only (reliably) do UI from the main thread.
I am working on performing some tasks in the background every 60 seconds. The background task is the server requests documents to be downloaded form the website. The main thread/UI seems to be locking when the request is done and I am saving the data to sqlite.
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
[NSThread detachNewThreadSelector:#selector(startTheBackgroundSync) toTarget:self withObject:nil];
[pool release];
- (void)startTheBackgroundSync {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
// [self performSelectorInBackground:#selector(moveSynctoBack) withObject:nil];
// [self performSelectorOnMainThread:#selector(makeMyProgressBarMoving) withObject:nil waitUntilDone:NO];
serverSync = [[[ServerSync alloc]init]autorelease];
while (1==1) {
serverSync.delegate = self;
[serverSync syncNow:nil];
[NSThread sleepForTimeInterval:120];
}
[pool release];
[serverSync release];
}
While the looping does not lock up the main thread, but when ASIHTtpRequest finished with the data it locks up the ui for a second.
The finished selector of an ASIHTTPRequest will always be performed on the main thread. Therefore you shouldn't do long running tasks there.
Instead of launching a new thread you can schedule a repeated NSTimer:
NSTimer* timer = [NSTimer scheduledTimerWithTimeInterval:120 target:self selector:#selector(backgroundSync) userInfo:nil repeats:YES];
... with the following action method:
-(void) backgroundSync
{
ServerSync* serverSync = [[[ServerSync alloc]init]autorelease];
serverSync.delegate = self;
[serverSync syncNow:nil];
}
Be sure that you use the startAsynchronous-method in ServerSync to start the request!
Furthermore I'd recommend to implement a singleton and then use it like that:
-(void)init {
[[ServerSync sharedSync] setDelegate:self];
}
-(void) backgroundSync
{
[[ServerSync sharedSync] syncNow];
}
One of my custom class has NSThread* as member variable.
I'm letting the thread exit by setting a variable(isNeedToExit).
Then call a release on the thread object [myThread release];
I intended the thread to exit first and release call gets called.
But on second thought, release call might get called before the thread notices the boolean value changed and exit.
(Because the thread is not constantly looking at the value to see if it needs to exit)
#property (nonatomic, retain) NSThread* myThread;
- (void) dealloc
{
...
[myThread release];
myThread = nil;
...
[super dealloc];
}
- (id) initMyClass
{
if(self = [super init] )
{
...
NSThread* aThread = [[NSThread alloc] initWithTarget: self selector:#selector(theThreadMain) object: nil];
self.myThread = aThread;
[aThread release];
[self.myThread start];
..
}
return self;
}
- (void) theThreadMain
{
NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
// Add your sources or timers to the run loop and do any other setup.
NSRunLoop *runloop = [NSRunLoop currentRunLoop];
[runloop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
do
{
// Start the run loop but return after each source is handled.
SInt32 result = CFRunLoopRunInMode(kCFRunLoopDefaultMode, 10, YES);
}
while (self.isNeedToExit == false);
[pool release];
SYSLOG(LOG_DEBUG, "thread exiting");
}
I thought of moving [myThread release] call (now at the class's dealloc) to last line of theThreadMain..
Not sure if this is the correct way to stop a thread when it is a member variable of another class.
Thank you
Actually, your initial assumption was more correct. Your current implementation, however, will not work because dealloc will never be called.
Your class is retained by NSThread in initWithTarget:selector:object and will not be released until the thread finishes executing. Therefore, your class instance will not be deallocated as long as the thread is executing.
One way to solve the problem is to add a "cancel" method to your class that stops the thread. E.g.:
- (void)cancel
{
isNeedToExit = YES;
[myThread release];
myThread = nil;
}
That will cause the thread to stop and will allow your class to be deallocated (once the thread stops.) You need to ensure that all users of your class know that "cancel" needs to be called before the class is released. E.g.:
[myObject cancel]; // Stop the thread
[myObject release];
myObject = nil;
A more traditional solution would be to simply subclass NSThread.