What is the correct way to update UI asynchronously? - ios

So I have this code:
int i = 3;
dispatch_async(queue, ^{
dispatch_async(dispatch_get_main_queue(), ^{
for (;;)
{
t1 = [NSTimer scheduledTimerWithTimeInterval:i target:self selector:#selector(updateUI) userInfo:nil repeats:NO];
}
});
});
And so what i'm doing is i'm updating the UI every 3 seconds in a game, but I want to be able to change the interval on which the timer goes off so I used an infinite for loop with the timer in it. My problem is that when I use the main queue it doesn't do it async but I need to use the main queue for updating the UI so I don't know what to do.

If you create NSTimer in main thread, then selector call in main thread.
- (void)updateTimer
{
// destroy timer
[t1 invalidate];
// start new timer
t1 = [NSTimer scheduledTimerWithTimeInterval:i target:self selector:#selector(updateUI) userInfo:nil repeats:NO];
}
- (void)updateUI
{
......
[self updateTimer];
}
Need call [self updateTimer] in start application, to begin the update UI.

Related

NStimer does not stop on invalidate

My timer does not stop even if i am doing "invalidate" and "nil" after reading other links. My code is as below:
#property(nonatomic,strong) NSTimer *mytimer;
- (void)viewDidLoad {
[self performSelectorOnMainThread:#selector(updateProgressBar:) withObject:nil waitUntilDone:NO];
<do some other work>
}
- (void) updateProgressBar :(NSTimer *)timer{
static int count =0;
count++;
NSLog(#"count = %d",count);
if(count<=10)
{
self.DownloadProgressBar.progress= (float)count/10.0f;
}
else{
NSLog(#"invalidating timer");
[self.mytimer invalidate];
self.mytimer = nil;
return;
}
if(count <= 10){
NSLog(#"count = %d **",count);
self.mytimer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:#selector(updateProgressBar:) userInfo:nil repeats:YES];
}
}
1) The timer goes on infinetly even when invalidating timer else condition is hit after count >10 and count keeps on incrementing.
2) i want to do this on a non-main thread . i want to continue in viewdidload() after starting the timer. How to do this ?
I visited other links on SO, all i understood was to call invalidate and nil on timer pointer. I am still facing problems. Could anyone tell me what i am missing here and what i can i do to run the updateProgressBar on background thread and update the progress bar ?
don't need to schedule a timer each time, schedule it once and timer will fire every second for example u can do like below,
- (void)viewDidLoad
{
[super viewDidLoad];
[self performSelectorOnMainThread:#selector(startTimerUpdate) withObject:nil waitUntilDone:NO]; //to start timer on main thread
}
//hear schedule the timer
- (void)startTimerUpdate
{
self.mytimer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:#selector(updateProgressBar:) userInfo:nil repeats:YES];
}
- (void) updateProgressBar :(NSTimer *)timer{
static int count =0;
count++;
NSLog(#"count = %d",count);
if(count<=10)
{
//self.DownloadProgressBar.progress= (float)count/10.0f;
NSLog(#"progress:%f",(float)count/10.0f);
}
else
{
NSLog(#"invalidating timer");
[self.mytimer invalidate];
self.mytimer = nil;
return;
}
if(count <= 10){
NSLog(#"count = %d **",count);
}
}
I think you are scheduling timer multiple time. I think 10 time. just schedule time one time or if require many time then invalidate it that many time as schedule.
Update according to comment : Schedule timer from viewdidload and addobserver means notification on task. when your task will completed invalidate timer. and update your progress in selector method of timer so when you invalidate it it will automatically stop progress bar.
Second thing : you should invalidate timer before moving another viewcontroller also because this objects remains live untill invalidate.
Hope this will hellp :)

Unable to stop NSTimer

I use this code for stopping NSTimer
[timer invalidate]
timer = nil;
It works fine for the first run. But, after I resume the timer with this code.
timer = [NSTimer scheduledTimerWithTimeInterval:5.0
target:self
selector:#selector(checkNewUpdates)
userInfo:nil
repeats:YES];
NSTimer won't stop anymore with [timer invalidate]
It look like multiple instance of timer is running simultaneously. You can do one thing, before start to run a new timer, check for previous instance of timer, and if timer instance is available, then invalidate it. After this start new instance
if(timer)
{
[timer invalidate];
timer = nil;
}
timer = [NSTimer scheduledTimerWithTimeInterval:5.0
target:self
selector:#selector(checkNewUpdates)
userInfo:nil
repeats:YES];
In apple's official document they said:
You must send this message from the thread on which the timer was
installed. If you send this message from another thread, the input
source associated with the timer may not be removed from its run loop,
which could prevent the thread from exiting properly.
If your timer is running on main thread, do this:
[timer performSelectorOnMainThread:#selector(invalidate) withObject:nil waitUntilDone:YES];
If it is on any other thread, lets call the thread myThread, then do this:
[timer performSelector:#selector(invalidate) onThread:myThread withObject:nil waitUntilDone:NO];
Hope this helps.. :)
Just invalidate the timer inside the selector that fires. That will ensure you have a pointer to the correct timer (which is probably why your invalidate call isn't working:
timer = [NSTimer scheduledTimerWithTimeInterval:5.0
target:self
selector:#selector(checkNewUpdates:)
userInfo:nil
repeats:YES];
Note the colon after checkNewUpdates:
Then, in your checkNewUpdates: method, do something like this:
- (void)checkNewUpdates:(NSTimer*)timer
{
// do somehting
// Then, check if the criteria for stopping the timer has been met, and invalidate it here.
if( self.shouldStopTimer ) // made up variable, use your own criteria.
{
[timer invalidate];
}
}
I know this doesnt answer your question per-se;
Can I suggest using polling mechanism instead of a timer? Ive had a world of trouble with NSTimers in the past and polling was a good alternative. Ive made a simple one below.
- (void) doPoll {
// do logic here
if (shoudStop) {
return;
}
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, X * NSEC_PER_SEC)),
dispatch_get_main_queue(), ^{
[self doPoll];
});
}
This is just a simple example, it does not stop retain cycles If you choose to try this, Yours should.
Hope it helps.

iOS Timer loop that executes a certain action every X minutes

I am trying to execute a certain block of code every x amount of time, but it seems that all I am doing is executing it during that time. Here's a block of my code.
while (TRUE) {
NSTimer *countDown = [NSTimer
scheduledTimerWithTimeInterval:(x)
target:self
selector:#selector(timerHandle)
userInfo:nil
repeats:YES];
}
Any ideas as to how to do it?
As written, this is an infinite loop, creating an NSTimer every loop iteration.
Try it without the while loop. This should cause [self timerHandle] to be invoked on interval x by a single background thread/timer. The Apple guide to NSTimer usage (including as others point out, how to properly stop your timed task) is here.
Try this: (It will call executeMethod on every 5 sec)
if (![NSThread isMainThread]) {
dispatch_async(dispatch_get_main_queue(), ^{
[NSTimer scheduledTimerWithTimeInterval:5.0
target:self
selector:#selector(executeMethod)
userInfo:nil
repeats:YES];
});
}
else{
[NSTimer scheduledTimerWithTimeInterval:5.0
target:self
selector:#selector(executeMethod)
userInfo:nil
repeats:YES];
}
Write the code you want to be executed in executeMethod method. Hope this helps.. :)

iphone development: nstimer calling a function

I have a void function which just have NSLog(#"Call me"); in its body.
I call it in my view in every ten seconds by using
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:10 target:self selector:#selector(yourMethod) userInfo:nil repeats:YES];
But I want it to stop it after 5 iterations. However it goes to infinity. How can I do that?
1) Keep a global variable, that increments from 0 to 5.
int i = 0;
2) Incement this variable inside your timer function..
-(void) yourFunction:(NSTimer*)timer{
//do your action
i++;
if(i == 5){
[timer invalidate];
}
}
3) When creating timer
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:10
target:self
selector:#selector(yourMethod:) // <== see the ':', indicates your function takes an argument
userInfo:nil
repeats:YES];
You should take one counter, increment it every time when your method get called, count for 5 and then invalidate your timer using below code.
[timer invalidate];
To destroy the timer from the current loop, you should call [timer invalidate];
To determine five occurrences, you need to maintain a variable and increment its count each time. If it is equal to 5, call invalidate method.
First of all you need to declare an int and declare your NSTimer *timer, so we can stop it:
#interface AppDelegate : UIViewController {
int myInt;
NSTimer *timer;
}
To start the NSTimer you'll need to change just a bit of the code:
timer = [NSTimer scheduledTimerWithTimeInterval:10 target:self selector:#selector(yourMethod) userInfo:nil repeats:YES];
Inside your void function you can make the verification to check if the code's running after 5 iterations:
- (void)myVoid{
NSLog(#"Call Me");
if (myInt == 5) {
[timer invalidate];
timer = nil;
}
myInt++;
}

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];
});

Resources