“This is the 14th day of my participation in the November Gwen Challenge. See details of the event: The Last Gwen Challenge 2021”.

NSTimer is a timer commonly used in iOS to repeat a task at a fixed interval. It is also relatively easy to use, but there has always been a problem with circular references. Let’s take a look at the use of circular references.

So let’s say I have a controller.

@interface MyViewController(a)@property (nonatomic, strong) NSTimer * timer;
@end
Copy the code

In viewDidLoad of our controller, we’re going to initialize a button.

- (void)viewDidLoad {
    [super viewDidLoad];
    UIButton * btn = [UIButton buttonWithType:UIButtonTypeCustom];
    [btn setTitle:@"begin" forState:UIControlStateNormal];
    [btn setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
    btn.frame = CGRectMake(20.100.120.80);
    [btn addTarget:self action:@selector(update) forControlEvents:UIControlEventTouchUpInside];
    [self.view addSubview:btn];
}
Copy the code

Click the button to initialize the timer.

- (void)update {
    self.timer = [NSTimer scheduledTimerWithTimeInterval:2 target:self selector:@selector(logNumber) userInfo:nil repeats:YES];
}
Copy the code

Execute the following method every 2 seconds.

- (void)logNumber {
    NSLog(@"22222");
}
Copy the code

It looks simple, and the code works fine, but the problem is that the timer is a property of the control, and when it initializes, the controller strongly references timer. In the initialization method of the timer, self (the controller) is strongly referenced by the timer as target. No doubt, a circular reference occurs, This will result in neither being released, which means that the controller does not execute the dealloc method when it is pushed out, and therefore the following method will not execute. Timer will not be released either.

- (void)dealloc {     
    [self.timer invalidate];
    self.timer = nil;
}
Copy the code

So what’s the solution? Here’s a way to do it: introduce the middleman. MyTimerMiddle is an intermediate class. It is responsible for initializing the timer, triggering the scheduled task, and destroying the timer. It is also responsible for connecting the target object that will eventually execute the task.

@interface MyTimerMiddle : NSObject
// Target is the last target to connect to. Note: weak is used here for reasons explained later.
@property (nonatomic, weak) id target;
@property (nonatomic, assign) SEL selector;

- (instancetype)initWithTimeInterval:(NSTimeInterval)interval
                              target:(id)target
                            selector:(SEL)selector;
/ / to destroy the timer
- (void)cleanup;
@end
Copy the code

Timer as one of its properties.

#import "MyTimerMiddle.h"

@interface MyTimerMiddle(a)@property (nonatomic, strong) NSTimer * timer;
@end
Copy the code

Provides a custom initialization method externally

- (instancetype)initWithTimeInterval:(NSTimeInterval)interval
                              target:(id)target
                            selector:(SEL)selector {
    if (self = [super init]) {
        self.target = target;
        self.selector = selector;
        self.timer = [NSTimer scheduledTimerWithTimeInterval:interval target:self selector:@selector(fetchData:) userInfo:nil repeats:YES];
    }
    return self;
}
Copy the code

Timed execution of the task, here simulated a download task, 3 seconds later back to the main thread execution, here is the real connection to the target object.

- (void)fetchData:(NSTimer *)timer {
    NSLog(@"Download.....");
    __weak typeof(self) weakSelf = self;
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        __strong typeof(self) sself = weakSelf;
        if (!sself) {
            return;
        }
        if (sself.target = = nil) {
            return;
        }
        id targrt = sself.target;
        SEL selector = sself.selector;
        if ([targrt respondsToSelector:selector]) {
            [targrt performSelector:selector withObject:@{@"name": @"mike"}]; }}); }Copy the code

Next is the destruction method of the timer.

- (void)cleanup {
    [self.timer invalidate];
    self.timer = nil;
}
Copy the code

When used, we declare an intermediate type property in the target controller.

@interface MyViewController(a)@property (nonatomic, strong) MyTimerMiddle * timerMid;
@end
Copy the code

Initialize the middle class. Notice that taerget is specified as self, because the target property was declared weak, so the middle class does not strongly reference self, thus avoiding a circular reference. ShowSome is the task that the target object will eventually execute on a regular basis.

self.timerMid = [[MyTimerMiddle alloc] initWithTimeInterval:5 target:self selector:@selector(showSome:)];
Copy the code

Finally, we need to clean up the intermediate classes when the controller is destroyed.

- (void)dealloc {
    NSLog(@"111");
    [self.timerMid cleanup];
}
Copy the code