Best time to invalidate NSTimer inside UIViewController to avoid retain cycle
You could avoid the retain cycle to begin with by, e.g., aiming the timer at a
StatusUpdate
object that holds a non-retained (weak) reference to your controller, or by having aStatusUpdater
that is initialized with a pointer your controller, holds a weak reference to that, and sets up the timer for you.You could have the view stop the timer in
-willMoveToWindow:
when the target window isnil
(which should handle the counterexample to-viewDidDisappear:
that you provided) as well as in-viewDidDisappear:
. This does mean your view is reaching back into your controller; you could avoid reaching in to grab the timer by just send the controller a-view:willMoveToWindow:
message or by posting a notification, if you care.Presumably, you're the one causing the view to be removed from the window, so you could add a line to stop the timer alongside the line that evicts the view.
You could use a non-repeating timer. It will invalidate as soon as it fires. You can then test in the callback whether a new non-repeating timer should be created, and, if so, create it. The unwanted retain cycle will then only keep the timer and controller pair around till the next fire date. With a 1 second fire date, you wouldn't have much to worry about.
Every suggestion but the first is a way to live with the retain cycle and break it at the appropriate time. The first suggestion actually avoids the retain cycle.
For @available(iOS 10.0, *) you could also use:
Timer.scheduledTimer(
withTimeInterval: 1,
repeats: true,
block: { [weak self] _ in
self?.updateStatus()
}
)
You can try with - (void)viewDidDisappear:(BOOL)animated
and then you should validate it again in - (void)viewDidAppear:(BOOL)animated
More here
One way around it is to make the NStimer hold a weak reference to your UIViewController. I created a class that holds a weak reference to your object and forwards the calls to that:
#import <Foundation/Foundation.h>
@interface WeakRefClass : NSObject
+ (id) getWeakReferenceOf: (id) source;
- (void)forwardInvocation:(NSInvocation *)anInvocation;
@property(nonatomic,assign) id source;
@end
@implementation WeakRefClass
@synthesize source;
- (id)init{
self = [super init];
// if (self) {
// }
return self;
}
+ (id) getWeakReferenceOf: (id) _source{
WeakRefClass* ref = [[WeakRefClass alloc]init];
ref.source = _source; //hold weak reference to original class
return [ref autorelease];
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
return [[self.source class ] instanceMethodSignatureForSelector:aSelector];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
[anInvocation invokeWithTarget:self.source ];
}
@end
and you use it like this:
statusTimer = [NSTimer scheduledTimerWithTimeInterval: 1 target: [WeakRefClass getWeakReferenceOf:self] selector: @selector(updateStatus) userInfo: nil repeats: YES];
Your dealloc method gets called (unlike before) and inside it you just call:
[statusTimer invalidate];