Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

NSTimer with weak self; why is dealloc not called?

Consider view controller with strong(or weak, the same) NSTimer property:

__weak __typeof(self) ws = self;
self.timer = [NSTimer scheduledTimerWithTimeInterval:2 target:ws selector:@selector(timerTigger:) userInfo:nil repeats:YES];

But why does this view controller not invoke dealloc method, whether I pass strong or weak reference to self?

Here is the detailed code:

#import "SecondViewController.h"

@interface SecondViewController ()

@property (nonatomic, weak) NSTimer *timer;

@end

@implementation SecondViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    self.view.backgroundColor = [UIColor whiteColor];
    __weak __typeof(self) ws = self;
    self.timer = [NSTimer scheduledTimerWithTimeInterval:2 target:ws selector:@selector(timerTigger:) userInfo:nil repeats:YES];

}

- (void)timerTigger:(id)timer {
    NSLog(@"do someting");
}

- (void)dealloc {
    NSLog(@"SecondViewController dealloc");
}
like image 679
huangxinyu Avatar asked Dec 10 '22 12:12

huangxinyu


1 Answers

NSTimer maintains strong reference to its target until the timer is invalidated. You don't get to choose whether NSTimer establishes a weak or strong reference. When you pass a weak reference, as long as ws is not nil by the time you start the timer (which it obviously won't be, in this case), NSTimer will establish a strong reference to whatever target pointed. Whether scheduledTimerWithTimeInterval establishes a strong or weak reference is not some inherent characteristic of the pointer that you passed to it, but rather a question of what that method does with that pointer it was provided.

To fix this strong reference behavior, you can adopt one of the following patterns:

  • use the block based rendition of NSTimer and use "weak self" pattern inside the block;

    @interface ViewController ()
    
    @property (nonatomic, weak) NSTimer *timer;
    
    @end
    
    @implementation ViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
    
        typeof(self) __weak weakSelf = self;
    
        self.timer = [NSTimer scheduledTimerWithTimeInterval:2 repeats:true block:^(NSTimer * _Nonnull timer) {
            [weakSelf timerTigger];
        }];
    }
    
    - (void)timerTigger {
        NSLog(@"do something");
    }
    
    - (void)dealloc {
        [self.timer invalidate];
    }
    
    
    @end
    
  • use GCD timer, which is also block based and therefore can also easily be configured to not keep strong reference; or

    @interface ViewController ()
    
    @property (nonatomic, strong) dispatch_source_t timer;
    
    @end
    
    @implementation ViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
    
        typeof(self) __weak weakSelf = self;
    
        self.timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue());
        dispatch_source_set_timer(self.timer, DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC, 0 * NSEC_PER_SEC);
        dispatch_source_set_event_handler(self.timer, ^{
            [weakSelf timerTigger];
        });
        dispatch_resume(self.timer);
    }
    
    - (void)timerTigger {
        NSLog(@"do something");
    }
    
    @end
    
  • use NSTimer with target/selector, but invalidate the timer somewhere logical (e.g. viewDidDisappear or the like).

    @interface ViewController ()
    
    @property (nonatomic, weak) NSTimer *timer;
    
    @end
    
    @implementation ViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
    
        self.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(timerTigger:) userInfo:nil repeats:true];
    }
    
    - (void)viewDidDisappear:(BOOL)animated {
        [super viewDidDisappear:animated];
    
        [self.timer invalidate];
    }
    
    - (void)timerTigger:(NSTimer *)timer {
        NSLog(@"do something");
    }
    
    @end
    
like image 147
Rob Avatar answered Dec 30 '22 18:12

Rob