theTimer = [NSTimer scheduledTimerWithTimeInterval:1.00 target:self selector:#selector(sendMessageHandler:) userInfo:nil repeats:YES];
//////////////////////////////////////////////////////
- (void)sendMessageHandler (NSTimer *) timer {
}
Ok so sendMessageHandler is triggering every second. But now, I want it to check the value of "theString" and if it changed value from the previous run, do something.
Can someone help me?
Thanks!
Make another string called prevString.
Make them equal each other initially. Then in the timer:
if ([theString isEqualToString:prevString]) {
//No change.
}
else {
//Change happened.
prevString = theString;
}
And for memory management in dealloc:
[theString release];
[prevString release];
Related
Thanks for the help. Experimenting with basic progressIndicator animation. This crashes, and noted: "TO8 Generator was compiled with optimization - stepping may behave oddly; variables may not be available." TO8 Generator is obviously the application.
Message:"No known class method for selector 'scheduledTimerWithTimeInterval:selector:userInfo:repeats:'
Any insight? Thank you again.
NSTimer *timer;
int count;
if(!timer)
{
timer = [NSTimer scheduledTimerWithTimeInterval:0.01
target:self
selector:#selector(checkThem:)
userInfo:nil
repeats:YES];
[progressBar startAnimation: self];
}
-(void)checkThem:(NSTimer *)aTimer
{
count++;
if(count > 100)
{
count = 0;
[timer invalidate];
timer = NULL;
[progressBar setDoubleValue:0.0];
[progressBar stopAnimation: self];
}
else
{
[progressBar setDoubleValue:(100.0 * count) / 100];
}
}
Andy's comment prompted me to check my Scheme config. "DeBug Executable" was enabled. As such, the function wouldn't work after compiling. I then disabled the option. All good after doing so. Sorry for the trouble.
The Problem
I am building an app where I am getting real-time data and updating a MKMapView. I get a batch of data every 10 seconds and between data sets from the webs service I am removing older data points while also adding the new ones.
Instead of updating them all at once I want spread out the animation of the new points I get from the data service over that 10 seconds so I create the 'real-time' feel and avoid as many stops and starts as I can.
Everything seems to be working great except the that the NSTimer is always finishing early... way early. It should loop through the new data over 10 seconds but it will typically finish looping through the new data set 4 to 5 seconds earlier then it should.
I have read through a lot of the Apple documentation and StackOverflow questions (below are two good ones for those that may be looking) :)
https://stackoverflow.com/a/18584973
https://developer.apple.com/library/ios/technotes/tn2169/_index.html#//apple_ref/doc/uid/DTS40013172-CH1-TNTAG8000
But it seems like most of the recommendations are made for gaming apps using CADisplayLink (but I am not building a gaming app) or that if you need to use a high performance timer that it should not be used continuously.
My timer does not need to be exact but if I could even get it within .5 seconds that would be great without having to add the overhead of some of the other options I have seen.
As always any thoughts / code / or directions you could point me would be greatly appreciated.
The Code
Once I collect the new data into arrays I create the time interval and start the timer with the code below
addCount = -1;
timerDelay = 10.0/[timerAdditions count];
delayTimer =[NSTimer scheduledTimerWithTimeInterval:timerDelay target:self selector:#selector(delayMethod) userInfo:nil repeats:YES];
That then fires this method that animates through adding and removing the my map annotations.
-(void) delayMethod {
addCount = addCount +1;
if (addCount >= [timerAdditions count]) {
[timerRemovals removeAllObjects];
[timerAdditions removeAllObjects];
addCount = -1;
[delayTimer invalidate];
delayTimer = nil;
} else {
[myMap addAnnotation:[timerAdditions objectAtIndex:addCount]];
[myMap removeAnnotation:[timerRemovals objectAtIndex:addCount]animated:YES];
}
}
UPDATE
I tried updating my timer through GCD. And what is odd is that the timing loop works every other dataset. Still do not have it working every tie but for some reason it seems to be tied to resetting the dispatch time or the timer interval.
-(void) delayMethod {
dispatch_time_t delay = dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * timerDelay); // How long
dispatch_after(delay, dispatch_get_main_queue(), ^(void){
addCount = addCount +1;
if (addCount >= [timerAdditions count]) {
[timerRemovals removeAllObjects];
[timerAdditions removeAllObjects];
addCount = -1;
//[delayTimer invalidate];
//delayTimer = nil;
} else {
NSLog(#"Delay fired count %i -- additoins %lu",addCount,(unsigned long)[timerAdditions count]);
[myMap addAnnotation:[timerAdditions objectAtIndex:addCount]];
[myMap removeAnnotation:[timerRemovals objectAtIndex:addCount]animated:YES];
[self delayMethod];
}
});
}
I am using timer as like this to do stuff with timer instance for more accurate result.
- (void)createTimer {
// start timer
if(gameTimer == nil)
gameTimer = [NSTimer timerWithTimeInterval:1.00 target:self selector:#selector(timerFired:) userInfo:nil repeats:YES] ;
[[NSRunLoop currentRunLoop] addTimer:gameTimer forMode:NSDefaultRunLoopMode];
timeCount = 725; // instance variable or you can set it as your need
}
- (void)timerFired:(NSTimer *)timer {
// update label
if(timeCount == 0){
[self timerExpired];
} else {
timeCount--;
if(timeCount == 0) {
// display correct dialog with button
[timer invalidate];
[self timerExpired];
}
}
//do your stuff here for particular time.
}
- (void) timerExpired {
// display an alert or something when the timer expires.
NSLog(#"Your time is over");
[gameTimer invalidate];
gameTimer = nil;
//do your stuff for completion of time.
}
in this take time interval you want. and also Increment decrement stuff as you require. And do stuff with timer fired and completed. In your situation if you don not want to expire timer than its ok. never invalidate timer instance and use only timer fired event to do stuff.
I went down a slightly different path thanks to another SO question I have referenced below. Basically by combining the two timers into one, setting that one timer to the fastest time interval I would need and managing the methods I need to at the changing intervals within the method called by the timer I have solved the problem I was seeing.
https://stackoverflow.com/a/25087473/2939977
timeAdder = (10.0/[timerAdditions count]);
timeCountAnimate = timeAdder;
[NSTimer scheduledTimerWithTimeInterval:0.11 target:self selector:#selector(timerFired) userInfo:nil repeats:YES];
- (void)timerFired {
timeCount += 0.111;
if (timeCount > 10.0) {
// call a method to start a new fetch
timeCount = 0.0;
timeCountAnimate =0.0;
[self timerTest];
}
if (timeCount > timeCountAnimate) {
timeCountAnimate += timeAdder;
[self delayMethod];
}
I'm trying to implement a timer but i'm getting bad access...I didn't use release neither in dealloc.
.h
NSTimer *_timerAnimacao;
#property (nonatomic, retain) NSTimer *timerAnimacao;
.m
#synthesize timerAnimacao = _timerAnimacao;
Here is where I call the timer.
if(acondition)
_timerAnimacao = [NSTimer scheduledTimerWithTimeInterval:0.06 target:self selector:#selector(changeTimeFilter:) userInfo:nil repeats:YES];
else
_timerAnimacao = [NSTimer scheduledTimerWithTimeInterval:0.06 target:self selector:#selector(changeTimeFilterHide:) userInfo:nil repeats:YES];
the changeTimeFilter works fine...but changeTimeFilterHide is getting BAD Access.
-(void)changeTimeFilter:(NSTimer *)theTimer{
if (tAnimacaoFilter<5) {
[_tbFilters setFrame:CGRectMake(_tbFilters.frame.origin.x, _tbFilters.frame.origin.y, _tbFilters.frame.size.width, 58*tAnimacaoFilter)];
}
if(tAnimacaoFilter < 10) {
tAnimacaoFilter++;
[_tbFeeds setFrame:CGRectMake(_tbFeeds.frame.origin.x, _tbFeeds.frame.origin.y + (4.4*tAnimacaoFilter), _tbFeeds.frame.size.width, _tbFeeds.frame.size.height - (4.4*tAnimacaoFilter))];
[imgBordaTbDireita setFrame:CGRectMake(imgBordaTbDireita.frame.origin.x, _tbFeeds.frame.origin.y - 4, imgBordaTbDireita.frame.size.width, imgBordaTbDireita.frame.size.height)];
[imgBordaTbEsquerda setFrame:CGRectMake(imgBordaTbEsquerda.frame.origin.x, _tbFeeds.frame.origin.y, imgBordaTbEsquerda.frame.size.width, imgBordaTbEsquerda.frame.size.height)];
}
else {
if(_timerAnimacao)
[_timerAnimacao invalidate];
[btnFilter setEnabled:YES];
}
}
-(void)changeTimeFilterHide:(NSTimer *)theTimer{
if (tAnimacaoFilter<5) {
[_tbFilters setFrame:CGRectMake(_tbFilters.frame.origin.x, _tbFilters.frame.origin.y, _tbFilters.frame.size.width, (58*5) - (58*tAnimacaoFilter))];
}
if(tAnimacaoFilter < 10) {
tAnimacaoFilter++;
[_tbFeeds setFrame:CGRectMake(_tbFeeds.frame.origin.x, _tbFeeds.frame.origin.y - (4.4*tAnimacaoFilter), _tbFeeds.frame.size.width, _tbFeeds.frame.size.height + (4.4*tAnimacaoFilter))];
[imgBordaTbDireita setFrame:CGRectMake(imgBordaTbDireita.frame.origin.x, _tbFeeds.frame.origin.y - 4, imgBordaTbDireita.frame.size.width, imgBordaTbDireita.frame.size.height)];
[imgBordaTbEsquerda setFrame:CGRectMake(imgBordaTbEsquerda.frame.origin.x, _tbFeeds.frame.origin.y, imgBordaTbEsquerda.frame.size.width, imgBordaTbEsquerda.frame.size.height)];
}
else {
if(_timerAnimacao)
[_timerAnimacao invalidate];
[btnFilter setEnabled:YES];
}
}
What you need to do on invalidate is to set NSTimer to nil like this:
[_timerAnimacao invalidate];
_timerAnimacao = nil;
If you don't do it your next commands will not work properly, e.g.:
if(_timerAnimacao)
[_timerAnimacao invalidate];
Please note: if(_timerAnimacao) is the same like if(_timerAnimacao == nil) !
Take a look at this one as well: How do I use NSTimer?
The instance of NSTimer needs to be in memory while you start and invalidate.
You can do that in two ways:
Set the NSTimer as a property of Appdelegate class, and use it wherever you need to.
NSTimer *timer=[(MyAppDelegate *)[[UIApplication sharedApplication] delegate].timer;
Create a new class and set NStimer as a property. Create a class method to return a singleton instance of the class.
static MyClass *obj = NULL;
(MyClass *)instance
{
#synchronized(self)
{
if (obj == NULL)
obj = [[self alloc] init];
}
return(obj);
}
I would prefer the second method.
Here's the problem: I have some code that goes like this
otherWinController = [[NotificationWindowController alloc] init];
for (int i = 0; i < 10; i++) {
[otherWinController showMessage:[NSString stringWithFormat:#"%d", i]];
NSLog(#"%d", i);
sleep(1);
}
where otherWinController is a subclass of NSWindowController that I am using to update my window as changes happen in code, and the alloc init method simply opens up the nib and shows the window. showMessage is a method that changes an NSTextView to display whatever text is in the parameter.
in the NSLog, the text changes every second and just counts to ten. However for the showMessage method, the text is blank for a full ten seconds and then just displays the number 10. any thoughts??
FTR, the showMessage method is simply
- (void)showMessage:(NSString *)text {
[[self message] setStringValue:text];
}
not that it should matter, that's pretty basic.
You can probably achieve the desired effect right inside your loop, if you explicitly give the run loop some time to run:
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow: 0.1]];
The problem is that you’re blocking the main thread in your for loop and user interface updates happen on the main thread. The main thread run loop will only spin (and consequently user interface updates will take place) after the method containing that for loop finishes executing.
If you want to update that text field every second, you should use a timer. For instance, considering otherWinController is an instance variable, declare a counter property in your class and:
otherWinController = [[NotificationWindowController alloc] init];
self.counter = 0;
[otherWinController showMessage:[NSString stringWithFormat:#"%d", self.counter]];
[NSTimer scheduledTimerWithTimeInterval:1.0
target:self
selector:#selector(updateCounter:)
userInfo:nil
repeats:YES];
In the same class, implement the method that’s called whenever the time has been fired:
- (void)updateCounter:(NSTimer *)timer {
self.counter = self.counter + 1;
[otherWinController showMessage:[NSString stringWithFormat:#"%d", self.counter]];
if (self.counter == 9) {
[timer invalidate];
// if you want to reset the counter,
// self.counter = 0;
}
}
Views don't get updated until the end of the run loop; your for loop doesn't let the run loop continue, so all the view updates you make are just done after your for loop exits.
You should either use an NSTimer or performSelector:withObject:afterDelay: to change the display in a loop-like fashion.
[NSTimer scheduledTimerWithTimeInterval:1
target:self
selector:#selector(changeTextFieldsString:)
userInfo:nil
repeats:YES];
Then your timer's action will change the view's image:
- (void)changeTextFieldsString:(NSTimer *)tim {
// currStringIdx is an ivar keeping track of our position
if( currStringIdx >= maxStringIdx ){
[tim invalidate];
return;
}
[otherWinController showMessage:[NSString stringWithFormat:#"%d", currStringIdx]]
currStringIdx++;
}
You also generally don't want to use sleep unless you're on a background thread, because it will lock up the rest of the UI; your user won't be able to do anything, and if you sleep long enough, you'll get the spinning beach ball.
I think that by calling sleep(1) you block the main thread, which must draw your changes. So the display is not updated. The task manager will not interrupt your function. You shouldn't use sleep in this case. Please take a look at NSTimer class. It has a static method scheduledTimerWithTimeInterval, which you should use.
static UIViewController *controller = nil;
.....
{
.....
otherWinController = [[NotificationWindowController alloc] init];
controller = otherWinController;
[NSTimer scheduledTimerWithTimeInterval:1.0f target:self selector:#selector(timerTick:) userInfo:nil repeats:YES]; //set timer with one second interval
.....
}
- (void) timerTick:(NSTimer*)theTimer {
static int i = 0;
[controller showMessage:[NSString stringWithFormat:#"%d", i]];
NSLog(#"%d", i);
if (++i == 10) {
[theTimer invalidate];
i = 0;
}
}
I've been searching for and attempting to program for myself, an answer to this question.
I've got a secondary thread running inside my mainView controller which is then running a timer which counts down to 0.
Whilst this timer is running the secondary thread which initiated the timer should be paused/blocked whatever.
When the timer reaches 0 the secondary thread should continue.
I've Experimented with both NSCondition and NSConditionLock with no avail, so id ideally like solutions that solve my problem with code, or point me to a guide on how to solve this. Not ones that simply state "Use X".
- (void)bettingInit {
bettingThread = [[NSThread alloc] initWithTarget:self selector:#selector(betting) object:nil];
[bettingThread start];
}
- (void)betting {
NSLog(#"betting Started");
for (int x = 0; x < [dealerNormalise count]; x++){
NSNumber *currSeat = [dealerNormalise objectAtIndex:x];
int currSeatint = [currSeat intValue];
NSString *currPlayerAction = [self getSeatInfo:currSeatint objectName:#"PlayerAction"];
if (currPlayerAction != #"FOLD"){
if (currPlayerAction == #"NULL"){
[inactivitySeconds removeAllObjects];
NSNumber *inactivitySecondsNumber = [NSNumber numberWithInt:10];
runLoop = [NSRunLoop currentRunLoop];
betLooper = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:#selector(betLoop) userInfo:nil repeats:YES];
[runLoop addTimer:[betLooper retain] forMode:NSDefaultRunLoopMode];
[runLoop run];
// This Thread needs to pause here, and wait for some input from the other thread, then continue on through the for loop
NSLog(#"Test");
}
}
}
}
- (void)threadKiller {
[betLooper invalidate];
//The input telling the thread to continue can alternatively come from here
return;
}
- (void)betLoop {
NSLog(#"BetLoop Started");
NSNumber *currentSeconds = [inactivitySeconds objectAtIndex:0];
int currentSecondsint = [currentSeconds intValue];
int newSecondsint = currentSecondsint - 1;
NSNumber *newSeconds = [NSNumber numberWithInt:newSecondsint];
[inactivitySeconds replaceObjectAtIndex:0 withObject:newSeconds];
inacTimer.text = [NSString stringWithFormat:#"Time: %d",newSecondsint];
if (newSecondsint == 0){
[self performSelector:#selector(threadKiller) onThread:bettingThread withObject:nil waitUntilDone:NO];
// The input going to the thread to continue should ideally come from here, or within the threadKiller void above
}
}
You can't run a timer on a thread and sleep the thread at the same time. You may want to reconsider whether you need a thread at all.
There's a few things that need to be pointed out here. First, when you schedule your timer:
betLooper = [NSTimer scheduledTimerWithTimeInterval:1
target:self
selector:#selector(betLoop:)
userInfo:nil
repeats:YES];
it's added to and retained by the current run loop by that method, so you don't need to do that manually. Just [myRunLoop run]. Your timer's selector argument is also invalid -- a timer's "target method" needs to look like this:
- (void)timerFireMethod:(NSTimer *)tim;
This also means that you don't need to retain the timer if all you want to do is invalidate it, since you will have a reference to it from inside that method.
Second, it's not clear what you mean by "this thread needs to sleep to wait for input". When you schedule that timer, the method (betLoop) is called on the same thread. If you were to sleep the thread, the timer would stop too.
You seem to be a little mixed up regarding methods/threads. The method betting is running on your thread. It is not itself a thread, and it's possible to call other methods from betting that will also be on that thread. If you want a method to wait until another method has completed, you simply call the second method inside the first:
- (void)doSomethingThenWaitForAnotherMethodBeforeDoingOtherStuff {
// Do stuff...
[self methodWhichINeedToWaitFor];
// Continue...
}
I think you just want to let betting return; the run loop will keep the thread running, and as I said, the other methods you call from methods on the thread are also on the thread. Then, when you've done the countdown, call another method to do whatever work needs to be done (you can also invalidate the timer inside betLoop:), and finalize the thread:
- (void)takeCareOfBusiness {
// Do the things you were going to do in `betting`
// Make sure the run loop stops; invalidating the timer doesn't guarantee this
CFRunLoopStop(CFRunLoopGetCurrent());
return; // Thread ends now because it's not doing anything.
}
Finally, since the timer's method is on the same thread, you don't need to use performSelector:onThread:...; just call the method normally.
You should take a look at the Threading Programming Guide.
Also, don't forget to release the bettingThread object that you created.
NSThread has a class method + (void)sleepForTimeInterval:(NSTimeInterval)ti. Have a look at this :).
NSThread Class Reference