NSTimer does not release the problem

@interface ViewController ()
@property (nonatomic, weak) NSTimer       *timer;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    __weak typeof(self) weakSelf = self;
    self.timer = [NSTimer timerWithTimeInterval:1 target:weakSelf selector:@selector(timerRun) userInfo:nil repeats:YES];
    [[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];
}
Copy the code

As shown in the code above, we create a timer and add it to the current runLoop, and also modify weakSelf with __weak. However, when we run the timer to leave the page, we find that the timer is not destroyed and is still executing. There is a bit of a puzzle here, we can use block and __weak to solve the problem of circular reference, but why not here?

According to the official document, we can see that the target we pass in will be strongly held by the timer. In this case, the target passed in is weakSelf, so the reference count of the memory space that weakSelf points to will be increased by 1. While block passing in weakSelf is captured by weak reference and will not add 1 to the reference count. And then there’s a phenomenon here where [NSRunLoop currentRunLoop] is resident, [NSRunLoop currentRunLoop] holds timer, timer strongly holds weakSelf, The memory space that weakSelf points to is self, and the reference count will be increased by 1, so the problem of not releasing will occur.

NSTimer does not release problem solutions

We have several solutions to the non-release problem as follows for your reference.

Use the block form

self.timer = [NSTimer scheduledTimerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) { NSLog(@"timer Execute ");}];Copy the code

The timer is processed when you leave the page

- (void)viewWillDisappear:(BOOL)animated{
    [super viewWillDisappear:animated];
    [self.timer invalidate];
    self.timer = nil;
}
Copy the code

Similarly, invalidate is called when you leave the page and the timer is set to nil, but there is a problem that the timer will be released when you push to the next page. So the recommended method is to release the pop only when it leaves the page.

- (void)didMoveToParentViewController:(UIViewController *)parent { if (parent == nil) { [self.timer invalidate]; self.timer = nil; }}Copy the code

The mediator pattern

- (void)viewDidLoad {
    [super viewDidLoad];

    self.target = [[NSObject alloc] init];
    class_addMethod([NSObject class], @selector(timerRun), (IMP) timerRunObjc, "v@:");
    self.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self.target selector:@selector(timerRun) userInfo:nil repeats:YES];
}

void timerRunObjc(id obj){
    NSLog(@"%s -- %@",__func__,obj);
}

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

Similarly, we can use an intermediary as a target to break the non-release problem. This kind of thinking comes from FBKVO, but although it can solve the problem, it is not concise enough. The logic code is written in the controller, we can optimize it.

@interface CXTimerViewController ()
@property (nonatomic, strong) CXTimerWapper *timerWapper;
@end

- (void)viewDidLoad {
    [super viewDidLoad];
    
    self.timerWapper = [[CXTimerWapper alloc] cx_initWithTimeInterval:1 target:self selector:@selector(timerRun) userInfo:nil repeats:YES];
}
Copy the code
@interface CXTimerWapper : NSObject - (instancetype)cx_initWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo; - (void)cx_invalidate; @end #import "CXTimerWapper.h" #import <objc/message.h> @interface CXTimerWapper() @property (nonatomic, weak) id target; @property (nonatomic, assign) SEL aSelector; @property (nonatomic, strong) NSTimer *timer; @end @implementation CXTimerWapper - (instancetype)cx_initWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo{ if (self == [super init]) { self.target = aTarget; // vc self.aSelector = aSelector; / / Method, vc release the if ([self. The target respondsToSelector: self. ASelector]) {Method Method = class_getInstanceMethod([self.target class], aSelector); const char *type = method_getTypeEncoding(method); class_addMethod([self class], aSelector, (IMP)fireHomeWapper, type); self.timer = [NSTimer scheduledTimerWithTimeInterval:ti target:self selector:aSelector userInfo:userInfo repeats:yesOrNo]; } } return self; } // run runloop void fireHomeWapper(CXTimerWapper *warpper){if (warpper. Target){// vc-dealloc void (*cx_msgSend)(void *,SEL, id) = (void *)objc_msgSend; cx_msgSend((__bridge void *)(warpper.target), warpper.aSelector,warpper.timer); }else{ // warpper.target [warpper.timer invalidate]; warpper.timer = nil; } } - (void)dealloc{ NSLog(@"%s",__func__); } @endCopy the code

So in the code above, we’ve got this layering idea, we’ve isolated the timer from the controller, we’ve put all the logic of the timer into the CXTimerWapper class, and one of the nice things we’ve done here is we haven’t written out the selector, It calls the selector from the target that’s passed in from the outside, so it’s a little bit more flexible, and in fireHomeWapper it checks for warpper.target, and if warpper.target is empty, that means that the outside caller has been released, So the timer is released.

Virtual base class

@interface ViewController ()
@property (nonatomic, strong) CXProxy       *proxy;
@end

- (void)viewDidLoad {
    [super viewDidLoad];
    self.proxy = [CXProxy proxyWithTransformObject:self];
    self.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self.proxy selector:@selector(timerRun) userInfo:nil repeats:YES];
}

- (void)dealloc{
    [self.timer invalidate];
    self.timer = nil;
}
Copy the code
@interface CXProxy : NSProxy + (instancetype)proxyWithTransformObject:(id)object; @end @interface CXProxy() @property (nonatomic, weak) id object; @end @implementation CXProxy + (instancetype)proxyWithTransformObject:(id)object{ CXProxy *proxy = [CXProxy alloc]; proxy.object = object; return proxy; } / / message fast forwarding - (id) forwardingTargetForSelector: (SEL) aSelector {return self. The object; } / / forward the self. The object / / - (NSMethodSignature *) methodSignatureForSelector SEL (SEL) {/ / / / if (self) object) {/ /} else { // NSLog(@" please collect stack111"); // } // return [self.object methodSignatureForSelector:sel]; // //} // //- (void)forwardInvocation:(NSInvocation *)invocation{ // // if (self.object) { // [invocation invokeWithTarget:self.object]; //}else{// NSLog(@" please collect stack"); // } // //} @endCopy the code

NSProxy is a root class that implements the NSObject protocol. It is described in the official Apple documentation as follows: NSProxy is an abstract base class that defines apis for objects that behave like proxies for other objects or that don’t exist. Typically, messages sent to the broker are forwarded to a real object or the broker itself causes loading (or converting itself into) a real object. The NSProxy base class can be used to transparently forward messages or for lazy initialization of expensive objects.

CXProxy class we implemented the forwardingTargetForSelector method, return to the self. The object, here the self. The object is the external controller, All methods sent to CXProxy are forwarded to self.object. This is similar to the mediator pattern described above, but it is important to note that the timer needs to be released in the Dealloc method when the controller is released.

Taking advantage of the Mid-Autumn festival holiday these days updated a few blogs, there are insufficient places also please point out more.