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()
})
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.
So to initiate an NSTimer we do this:
timer = [NSTimer scheduledTimerWithTimeInterval:0.35 target:self selector:#selector(timerMethod) userInfo:nil repeats:YES];
But what if I want this to run every 0.3 - 0.7 seconds (randomly). I can't do arc4random because then it would choose a number and stick to it.
The only way I've thought of is invalidating it every time it run the 'timerMethod' and then setting a new random time for it but I'm concerned that that will have an effect on the performance.
Is there another way, better to do this?
Instead of using a timer, use a series of [self performSelector:#selector(timerMethod) withObject:nil afterDelay:<random value>] calls. It'll look roughly like tail recursion — timerMethod will routinely schedule a future call to itself somewhere within it.
Also be mindful of retain cycles. Both NSTimer and performSelector:... retain their targets. You could either decline to use either and instead use dispatch_after having captured only a weak reference, or use an approximate two-stage deallocation where a non-dealloc call explicitly invalidates the timer or sets a flag to tell you not to schedule another call to timerMethod.
The GCD solution would look like:
- (void)timerMethod
{
// schedule the next call to timerMethod, keeping only a weak
// reference to self so as not to extend the lifecycle
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW,
(int64_t)(<random value> * NSEC_PER_SEC));
__weak typeof(self) weakSelf = self;
dispatch_after(popTime, dispatch_get_main_queue(),
^{
[weakSelf timerMethod];
});
// ... and do whatever else here ...
}
If you avoid the convenience method and use the NSTimer init method, you get a chance to setTolerance
Another alternative is to create a makeTimer method. In it invalidate the timer if it is not nil. Create a new timer using the init method and set a random interval.
Use the makeTimer method to make your timer.
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.
Everytime I am finished with my NSTimer, I want to invalidate it and create a new interval but it keeps the old interval as well as new interval. I want to invalidate NSTimes once I click the offButton. The timer stops printing "Working" but when I call my method with a different interval, it prints "Working" for both intervals.
My code is something like this:
-(void) fireTimer{
NSString *textValue = [sliderLabel text];
float value = [textValue floatValue];
[NSTimer scheduledTimerWithTimeInterval:value target:self selector:#selector(vibrate:) userInfo:nil repeats:YES];
}
- (void) vibrate:(NSTimer*)timer {
if(_offButton.selected){
[timer invalidate];
timer=nil;
}
NSLog(#"Working");
}
You aren't following the MVC design pattern by getting your values directly from the UITextField. Those values should be stored in a model object, with the MVC pattern being used to get any new values from the text field into the model. Your current implementation is very delicate and will break in the slightest breeze. It also requires this code to have access to the UI elements, which is very inflexible; it will be better to give it access to just the model object.
Store the NSTimer * as an instance variable, and note that if you are using ARC then the NSTimer retains the target (!!) so make this instance variable __weak to break the retain-cycle.
If you want your timer to repeat then there is no need to reset it at all; this only needs to be done if the user changes the time (see point 1!).
Move the if (button_selected) do_vibrate; code into the timer fired method.
The invalidation code that you use itself is correct. But it would be easier for you to keep a reference to your timer as an ivar or property.
In that case you would definetly avoid making multiple instances of a timer.