Weak reference to NSTimer target to prevent loop persistence

I am using NSTimer as follows:

 timer = [NSTimer scheduledTimerWithTimeInterval:30.0f target:self selector:@selector(tick) userInfo:nil repeats:YES]; 

Of course, NSTimer saves a target that creates a save loop. In addition, self not a UIViewController, so I have nothing like viewDidUnload where I can invalidate the timer to break the loop. So I'm wondering if I can use a weak link:

 __weak id weakSelf = self; timer = [NSTimer scheduledTimerWithTimeInterval:30.0f target:weakSelf selector:@selector(tick) userInfo:nil repeats:YES]; 

I heard that the timer must be invalid (I think to free it from the start loop). But we could do it in our dealloc, right?

 - (void) dealloc { [timer invalidate]; } 

Is this a viable option? I have seen many ways people deal with this problem, but I have not seen it.

+42
objective-c automatic-ref-counting nstimer retain-cycle
May 29 '13 at 18:54
source share
7 answers

Suggested code:

 __weak id weakSelf = self; timer = [NSTimer scheduledTimerWithTimeInterval:30.0f target:weakSelf selector:@selector(tick) userInfo:nil repeats:YES]; 

leads to (i) weak reference being made to oneself; (ii) this weak reference is read to indicate a pointer to an NSTimer . This will not result in an NSTimer with a weak link. The only difference between this code and the use of the __strong link is that if self is freed between the two specified lines, you pass nil to the timer.

The best you can do is create a proxy object. Something like:

 [...] @implementation BTWeakTimerTarget { __weak target; SEL selector; } [...] - (void)timerDidFire:(NSTimer *)timer { if(target) { [target performSelector:selector withObject:timer]; } else { [timer invalidate]; } } @end 

Then you will do something like:

 BTWeakTimerTarget *target = [[BTWeakTimerTarget alloc] initWithTarget:self selector:@selector(tick)]; timer = [NSTimer scheduledTimerWithTimeInterval:30.0 target:target selector:@selector(timerDidFire:) ...]; 

Or add a class method to BTWeakTimerTarget of the form +scheduledTimerWithTimeInterval:target:selector:... to create a more accurate form of this code. You probably want to open a real NSTimer so that you can invalidate it, otherwise the set rules will be:

  • the real target is not saved by the timer;
  • the timer will fire once after the launch (and possibly completed) of the real target, but this shooting will be ignored, and then the timer is invalid.
+67
May 29 '13 at 19:39
source share

If you are not worried about millisecond accuracy of timer events, you can use dispatch_after and __weak instead of NSTimer to do this. Here's the code template:

 - (void) doSomethingRepeatedly { // Do it once NSLog(@"doing something โ€ฆ"); // Repeat it in 2.0 seconds __weak typeof(self) weakSelf = self; double delayInSeconds = 2.0; dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC)); dispatch_after(popTime, dispatch_get_main_queue(), ^(void){ [weakSelf doSomethingRepeatedly]; }); } 

No NSTimer @property, no invalidate / runloop property and proxy object, just a simple method.

The disadvantage of this approach is that (unlike NSTimer ) the execution time of the block (containing [weakSelf doSomethingRepeatedly]; ) will affect event scheduling.

+26
Nov 27 '13 at 10:19
source share

iOS 10 and macOS 10.12 Sierra introduced a new method +scheduledTimerWithTimeInterval:repeats:block: so you might grab self weakly simply, like:

 __weak MyClass* weakSelf = self; _timer = [NSTimer scheduledTimerWithTimeInterval:1.0 repeats:YES block:^(NSTimer* t) { MyClass* _Nullable strongSelf = weakSelf; [strongSelf doSomething]; }]; 

Equivalence in Swift 3:

 _timer = Timer(timeInterval: 1.0, repeats: true) { [weak self] _ in self?.doSomething() } 

If you still need to target on iOS 9 or lower (what you need at the moment), this method cannot be used, so you still have to use the code in other answers.

+16
Sep 29 '16 at 18:10
source share

In Swift, I defined a helper class WeakTimer :

 /// A factory for NSTimer instances that invoke closures, thereby allowing a weak reference to its context. struct WeakTimerFactory { class WeakTimer: NSObject { private var timer: NSTimer! private let callback: () -> Void private init(timeInterval: NSTimeInterval, userInfo: AnyObject?, repeats: Bool, callback: () -> Void) { self.callback = callback super.init() self.timer = NSTimer(timeInterval: timeInterval, target: self, selector: "invokeCallback", userInfo: userInfo, repeats: repeats) } func invokeCallback() { callback() } } /// Returns a new timer that has not yet executed, and is not scheduled for execution. static func timerWithTimeInterval(timeInterval: NSTimeInterval, userInfo: AnyObject?, repeats: Bool, callback: () -> Void) -> NSTimer { return WeakTimer(timeInterval: timeInterval, userInfo: userInfo, repeats: repeats, callback: callback).timer } } 

And then you can use it like this:

 let timer = WeakTimerFactory.timerWithTimeInterval(interval, userInfo: userInfo, repeats: repeats) { [weak self] in // Your code here... } 

The returned NSTimer has a weak reference to self , so you can call its invalidate method on deinit .

+4
Dec 07 '14 at 19:53
source share

It doesn't matter if weakSelf is weak, the timer still saves the object, so the save loop is saved. Since the timer is stored in the start loop, you can (and I suggest) hold a weak pointer to the timer:

 NSTimer* __weak timer = [NSTimer scheduledTimerWithTimeInterval:30.0f target: self selector:@selector(tick) userInfo:nil repeats:YES]; 

You do the right thing about invalidity.

+3
May 29 '13 at 19:21
source share

Swift 3

App Goal <iOS 10 :

Custom WeakTimer ( GitHubGist ):

 final class WeakTimer { fileprivate weak var timer: Timer? fileprivate weak var target: AnyObject? fileprivate let action: (Timer) -> Void fileprivate init(timeInterval: TimeInterval, target: AnyObject, repeats: Bool, action: @escaping (Timer) -> Void) { self.target = target self.action = action self.timer = Timer.scheduledTimer(timeInterval: timeInterval, target: self, selector: #selector(fire), userInfo: nil, repeats: repeats) } class func scheduledTimer(timeInterval: TimeInterval, target: AnyObject, repeats: Bool, action: @escaping (Timer) -> Void) -> Timer { return WeakTimer(timeInterval: timeInterval, target: target, repeats: repeats, action: action).timer! } @objc fileprivate func fire(timer: Timer) { if target != nil { action(timer) } else { timer.invalidate() } } } 

Using:

 let timer = WeakTimer.scheduledTimer(timeInterval: 2, target: self, repeats: true) { [weak self] timer in // Place your action code here. } 

timer is an instance of the standard timer class, so you can use all available methods (e.g. invalidate , fire , isValid , fireDate , etc.).
The timer instance will be freed if self freed or a timer job is executed (for example, repeats == false ).

App Goal> = iOS 10 :
Standard timer implementation:

 open class func scheduledTimer(withTimeInterval interval: TimeInterval, repeats: Bool, block: @escaping (Timer) -> Swift.Void) -> Timer 

Using:

 let timer = Timer.scheduledTimer(withTimeInterval: 2, repeats: true) { [weak self] timer in // Place your action code here. } 
+2
Dec 6 '16 at 20:11
source share

If you use Swift, this is an auto cancel timer:

https://gist.github.com/evgenyneu/516f7dcdb5f2f73d7923

The timer automatically cancels itself on deinit .

 var timer: AutoCancellingTimer? // Strong reference func startTimer() { timer = AutoCancellingTimer(interval: 1, repeats: true) { print("Timer fired") } } 
0
Jul 04 '15 at 5:11
source share



All Articles