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.
Related
As we know we need to use a weak reference inside a block to break the retain cycle, like so:
__weak id weakSelf = self;
[self doSomethingWithABlock:^() {
[weakSelf doAnotherThing];
}]
However weak references can not break the retain cycle caused by a NSTimer.
__weak id weakSelf = self;
timer = [NSTimer scheduledTimerWithTimeInterval:30.0f
target:weakSelf
selector:#selector(tick)
userInfo:nil
repeats:YES]; // No luck
What's the difference? How can the timer still retain the target?
The whole problem with the selector-based NSTimer technique is that it establishes a strong reference to the object you pass to it. So whether the variable you used to hold the reference to the target you passed to scheduledTimerWithTimeInterval was, itself, strong or weak, is immaterial. Assuming the target reference wasn't nil by the time the selector-based scheduled timer was scheduled, NSTimer will establish its own strong reference. The "weak" vs "strong" nature of the references in calling code only dictates where ARC will place its own memory management calls in the caller's code, but the target is just a simple pointer, and none of this weak vs strong information is conveyed to NSTimer. The selector-based NSTimer will establish its own strong reference that is not resolved until the timer is invalidated.
This is why, when we want to invalidate a timer built via the selector-based method, we have to it in viewDidDisappear or the like, rather than dealloc.
Note, scheduledTimerWithTimeInterval now has a block-based variation for iOS 10 and later, so you can enjoy the weak reference pattern of blocks if you don't have to support earlier iOS versions:
typeof(self) __weak weakSelf = self;
[NSTimer scheduledTimerWithTimeInterval:30 repeats:true block:^(NSTimer * _Nonnull timer) {
// use weakSelf here
}];
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'm new in xcode and objective-c and trying to developing an app. I would like to set up a timer in some of pages for calculating how much time user spend in that page. In the app, I have 5 theme pages, each pages contains a table view to another 3 sub-pages. I would like to add a counter to these 3 pages (5*3) but not including theme pages themselves. The page shift is controlled by navigation bar. I have put some codes as follows in .m file of viewcontroller.
- (void)viewDidLoad
{
[super viewDidLoad];
//timer
timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self
selector:#selector(handleTimer)
userInfo:nil
repeats:YES];
}
-(void)handleTimer
{
MainInt += 1;
self.TimeLabel.text = [NSString stringWithFormat:#"%d",MainInt];
}
and some code in .h file (Brett is one of the 3 sub-pages.)
#interface Brett : UIviewController
{
NSTimer *timer;
int MainInt;
}
# property (weak, nonatomic) IBOutlet UILable *TimLable;
Every time when I leave the page and go back again the counter is always count from 0. Anyone can help to solve this problem??
Every time it Count start form 0, I guess because of you are creating Brett object every time when you push it to navigation.
Create Global Timer in appDelegate class and use it,
in viewWillAppear start timer and in viewWillDisappear pause that timer.
Alternative to the other answer, you can just make the MainInt a static variable
static int MainInt;
What I understood from your question is to simply keep track of the amount of time a user spend in sub pages. If my understanding is correct, then you may try following.
Create and start timer as like now you are doing.
On viewWillDisAppear method of your controller, just update the global variable or NSUserDefaults value like this:
//Get the previous time available in userdefaults
int counter = [[NSUserDefaults standardUserDefaults] integerForKey:#"ScreenACounter"];
//Update the key with existing counter value + current timer value.
[[NSUserDefaults standardUserDefaults] setInteger:counter + currentTimer.Value forKey:#"HighScore"];
Hope this helps.
When I execute this code:
[NSTimer scheduledTimerWithTimeInterval:0.5 target:self selector:#selector(showButtons) userInfo:nil repeats:NO];
do I need to nil it or release it, ot whatever for memory management?
I am using ARC
Yes, NSTimer will maintain a strong reference to the target, which can cause (especially in repeating timers) strong reference cycles (a.k.a. retain cycles). In your example, though, the timer does not repeat, and is delayed only 0.5, so worst case scenario, you will have a strong reference cycle that will automatically resolve itself in 0.5 seconds.
But a common example of an unresolved strong reference cycle would be to have a UIViewController with a NSTimer property that repeats, but because the NSTimer has a strong reference to the UIViewController, the controller will end up being retained.
So, if you're keeping the NSTimer as an instance variable, then, yes, you should invalidate it, to resolve the strong reference cycle. If you're just calling the scheduledTimerWithTimeInterval, but not saving it to an instance variable (as one might infer from your example), then your strong reference cycle will be resolved when the NSTimer is complete.
And, by the way, if you're dealing with repeating NSTimers, don't try to invalidate them in dealloc of the owner of the NSTimer because the dealloc obviously will not be called until the strong reference cycle is resolved. In the case of a UIViewController, for example, you might do it in viewDidDisappear.
By the way, the Advanced Memory Management Programming Guide explains what strong reference cycles are. Clearly, this is in a section where they're describing the proper use of weak references, which isn't applicable here (because you have no control over the fact that NSTimer uses strong references to the target), but it does explain the concepts of strong reference cycles nicely.
If you don't want your NSTimer to keep a strong reference to self, in macOS 10.12 and iOS 10, or later, you can use the block rendition and then use the weakSelf pattern:
typeof(self) __weak weakSelf = self;
[NSTimer scheduledTimerWithTimeInterval:0.5 repeats:false block:^(NSTimer * _Nonnull timer) {
[weakSelf showButtons];
}];
By the way, I notice that you're calling showButtons. If you're trying to just show some controls on your view, you could eliminate the use of the NSTimer altogether and do something like:
self.button1.alpha = 0.0;
self.button2.alpha = 0.0;
[UIView animateWithDuration:0.25
delay:0.5
options:UIViewAnimationOptionCurveEaseInOut | UIViewAnimationOptionAllowUserInteraction
animations:^{
self.button1.alpha = 1.0;
self.button2.alpha = 1.0;
}
completion:nil];
This doesn't suffer the retain issues of NSTimer objects, and performs both the delay as well as the graceful showing of the button(s) all in one statement. If you're doing additional processing in your showButtons method, you can put that in the completion block.
If you are saving it in a property, then yes, you do need to set it to nil after it fired the selector.
It's also safe to save it in case your class gets deallocated for whatever reason, so that you can [timer invalidate] if you need to.
Yes, you can use: myTimer=[NSTimer scheduledTimerWithTimeInterval:0.5 target:self selector:#selector(showButtons) userInfo:nil repeats:NO];
And then in your viewDidDisappear [myTimer invalidate]
Use the new method of Timer that uses closures
timer = Timer.scheduledTimer(withTimeInterval: 0.5, repeats: true, block: {[weak self] (timer) in
print("Tick tock")
guard let ws = self else { return }
//some action that repeats
ws.myViewControllerMethod()
})
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.