I don't want to create NSTimer object. How do I invalidate timer? I want to invalidate timer in viewWillDisappear.
-(void) viewDidLoad
{
[super viewDidLoad];
[NSTimer scheduledTimerWithTimeInterval:10 target:self selector:#selector(onTimer:) userInfo:nil repeats:YES];
}
A
you have to hold on to the timer you create:
#interface MONObject ()
#property (nonatomic, retain) NSTimer * timerIvar;
#end
#implementation MONObject
...
- (void)viewDidLoad
{
[super viewDidLoad];
self.timerIvar = [NSTimer scheduledTimerWithTimeInterval:10 target:self selector:#selector(onTimer:) userInfo:nil repeats:YES];
}
- (void)invalidateTimer
{
[self.timerIvar invalidate];
self.timerIvar = nil;
}
- (void)viewWillDisappear:(BOOL)animated
{
...
[self invalidateTimer];
}
B
another option would be to invalidate the timer that is passed in the callback, but that won't occur within viewDidUnload:. therefore, it doesn't quite apply in this scenario:
- (void)onTimer:(NSTimer *)pTimer
{
[pTimer invalidate];
}
If you want to be able to cancel the timer, you have to refer to the timer you’re cancelling, and that means you have to keep the pointer to the timer around, see justin’s answer.
Keeping a reference to the timer is the right way to do it, but for the sake of completeness you may also use the -performSelector:withObject:afterDelay: method as a poor man’s timer. That call may be invalidated using +cancelPreviousPerformRequestsWithTarget:. Sample code:
- (void) viewDidLoad
{
[super viewDidLoad];
[self performSelector:#selector(timerTick) withObject:nil afterDelay:10];
}
And then:
- (void) viewWillDisappear
{
[NSObject cancelPreviousPerformRequestsWithTarget:self];
[super viewWillDisappear];
}
But this is not the right way to do it, because there might be other perform-selector requests pending on your object that you would cancel. It’s best to keep your timer around, that way you know exactly what you’re cancelling.
By the way, it’s also probably a bad idea to run a timer in -viewDidLoad. View loading may happen anytime, without any relation to view being displayed.
Maybe this method can help you:
[self performSelector:#selector(onTimer:) withObject:nil afterDelay:10];
If you don't want to hold on to your timer, the NSTimer object will be passed to the timer method (in your case onTimer:), so in that method you could check whether the timer is still needed and invalidate it. However, you will run into trouble if the view comes back before you invalidated the timer, and you create a new one.
By far the best way is to store the timer into an instance variable. It works, no clever tricks, and you'll know six months later what you did. I'd probably write a
#property (readwrite, nonatomic) BOOL hasTimer;
getter returns YES iff the timer is not nil, setter invalidates the timer or creates a new one.
Related
In my app I have a following piece of code:
__weak __typeof(self)weakSelf = self;
_pingTimer = [NSTimer scheduledTimerWithTimeInterval:5.0
repeats:YES
block:^(NSTimer * _Nonnull timer)
{
__strong __typeof(weakSelf)strongSelf = weakSelf;
[strongSelf pingWithBlock:nil];
}];
this works perfectly in iOS 10+, but I need the app to support iOS 9 as well. So I needed to provide a method that would work for both.
I tried this:
__weak __typeof(self)weakSelf = self;
_pingTimer = [NSTimer scheduledTimerWithTimeInterval:5.0
target:weakSelf
selector:#selector(pingWithBlock:)
userInfo:nil
repeats:YES];
pingWithBlock method is defined in the same class, it's an instance method.
But this doesn't seem to work, meaning I get a bad memory access crash.
If anyone has any suggestions it will be highly appreciated.
EDIT:
thanks to #dgatwood explanations code below fixes the issue
- (void)autoPing
{
_pingTimer = [NSTimer scheduledTimerWithTimeInterval:self.autoCheckInterval
target:self
selector:#selector(pingWithBlock)
userInfo:nil
repeats:YES];
}
-(void)pingWithBlock
{
[self pingWithBlock:nil];
}
This is kind of odd. NSTimer retains its target. Maybe that doesn't happen in this case because of the __weak, but I thought it did anyway. *shrugs*
Either way, this sounds like a multithreading race condition:
Your timer isn't retaining the object, so it could go away at any time.
Something else is retaining the object.
The timer is scheduled in the runloop of the thread that was running when the timer was constructed.
That something else disposes of the reference to the object in another thread.
The timer fires in the first thread and the zeroing weak reference hasn't zeroed because the object is still halfway through destroying itself.
A crash occurs.
The best fix is to let the timer retain the target object (by removing all the weakSelf stuff). If the timer is a repeating timer, provide a method to allow the code that disposes of the enclosing object to cancel that timer, and be careful to always call it.
I am trying to make a timer app. I am fine with Play button but I couldnt get Pause button working. I have seen some tutorials on Timer Apps and most of them have only used: [timer invalidate] code for that method that solely stops the time that is currently being shown in the label (display). Even that code doesn't work for me so I tried doing this which makes kinda more sense but still, of no luck.
#implementation ViewController
int timerCounter=0;
NSTimer *timer;
NSString *label;
BOOL isPaused=NO;
-(IB Action) playButton:(id)sender{
[timer invalidate];
isPaused=NO;
timer=[NSTimer scheduledTimerWithTimeInterval:1 target:self selector: #selector(tick) userInfo:nil repeats:YES];
}
-(IBAction) pauseButton:(id)sender{
[timer invalidate];
isPaused=YES;
label=[NSString stringWithFormat:#"%d",timerCounter];
_labelTimer.text=label;
}
-(void) tick{
if (isPaused==NO){
timerCounter++;
}
label=[NSString stringWithFormat:#"%d",timerCounter];
_labelTimer.text=label;
}
The NSTimer API do not have any method for pausing. What is available is either fire or invalidate. About your code, You are using global variables - not a good practice, most probably the instance of timer you are calling is not the same, remove them and add a property in the class extension in .m instead:
#property (nonatomic, strong) NSTimer * timer;
you then address that property with self.timer.
If this does not help, check if the button call the method when you press it.
I am having trouble with NSTimer. The below code is called from a method and it creates an NSTimer and sets it to the instance variable self.syncDelay. The if statement at the end of the method checking if the timer is valid gets called.
However when I call this method again before the timer runs out, the if statement at the top of the method doesn't get called. Which, I am guessing, is why [self.syncDelay invalidate]; doesn't invalidate the timer.
I have a property defined as: #property (nonatomic, strong) NSTimer *syncDelay;
The functionality I am looking for is the timer being invalidated when the method is called for the second time before the timer runs out and creating a new timer. Thus effectively resetting the timer.
EDIT: When the method gets called for the second the NSTimer is nil. Which is strange as I am keeping a strong pointer to it.
else {
if ([self.syncDelay isValid]) {
NSLog(#"Timer valid at start of method");
}
[self.syncDelay invalidate];
self.syncDelay = nil;
NSInvocation *timerInvocation = [NSInvocation invocationWithMethodSignature:
[self methodSignatureForSelector:#selector(testMethod:)]];
// configure invocation
[timerInvocation setSelector:#selector(testMethod:)];
[timerInvocation setTarget:self];
[timerInvocation setArgument:&className atIndex:2]; // argument indexing is offset by 2 hidden args
self.syncDelay = [NSTimer scheduledTimerWithTimeInterval:10
invocation:timerInvocation
repeats:NO];
if ([self.syncDelay isValid]) {
NSLog(#"Timer valid at end of method");
}
}
I'm trying to start a NSTimer in my UIView class called "ClockView" with a method as selector that manipulates an initial float which was declared in the ViewController "ClockViewController".
My ClockViewController declares int timerIntWhite as an integer (for example 500). My ClockView needs this Value for the - (void)start method which runs a method called - (void)updateWhiteClock every second:
- (void)start {
timerIntWhite = PLEASE HELP ME AT THIS POINT!;
randomTimerWhite = [NSTimer scheduledTimerWithTimeInterval:(1.0/1.0)target:self selector:#selector(updateWhiteClock) userInfo:nil repeats:YES];
}
Is it possible to access the integer of ClockViewController in ClockView?
Try the following:
In your ClockView also declare the variable (property) int timerIntWhite and set this variable from your View Controller after the View gets created, for example, in viewDidLoad.
-(void)viewDidLoad {
[super viewDidLoad];
self.view.timerIntWhite = self.timerIntWhite;
}
After doing this, ClockView can access it's own timerIntWhite variable:
- (void)start {
timerIntWhite = self.timerIntWhite;
randomTimerWhite = [NSTimer scheduledTimerWithTimeInterval:(1.0/1.0)target:self selector:#selector(updateWhiteClock) userInfo:nil repeats:YES];
}
I'm assuming that your ClockViewController class knows that its view IS-A ClockView. This is very important! Otherwise you'll get a warning.
I also want to mention that according to the MVC rules it's a better idea if your ClockViewController class takes care of the NSTimer. Views should be used to display information to the user only.
Hope this helps!
When testing my app for memory leaks I discovered that whenever I start NSTimer with an interval, it shows that CFArray (store-deque) and CFArray (mutable-variable) keeps growing in size. In my actual app Malloc 16 and Malloc 32 etc increases in size alongside with the CFArray.
Question: how do I stop this "leak"?
code: .h
#interface ViewController : UIViewController
{
NSTimer *timerClock;
int timer;
}
#end
code: .m
- (void)viewDidLoad
{
[super viewDidLoad];
timer = 0;
timerClock = [NSTimer scheduledTimerWithTimeInterval:0.01 target:self selector:#selector(counter) userInfo:nil repeats:YES];
}
- (void)counter
{
}
#end
Are you seeing this when you dismiss your view controller? I ask this because a repeating NSTimer keeps a strong reference to its target and if you don't invalidate the timer when you dismiss the view controller, you'll leak both the timer and the controller itself because you will have a strong reference cycle (a.k.a. a retain cycle) between the controller and the NSTimer.
By the way don't try to invalidate in the controller's dealloc method, because with the strong reference cycle, dealloc will never get called. Often people will invalidate in viewDidDisappear. And, clearly, if you're going to invalidate your timer in viewDidDisappear, you probably should be creating it in viewDidAppear rather than viewDidLoad, to make sure you balance your creation of the timer with its invalidate calls.