preface

Using NSTimer or CADisplayLink can easily create circular references if you are not careful. The timer strongly references target regardless of whether the target uses the weak or strong modifier.

The runloop itself also strongly references the timer, causing the runloop to reference the timer and the timer to reference the target. If target is a controller, the controller cannot be released.

To solve this problem, break the chain of references.

1, viewWillDisappear

In View Disappear, call the timer’s invalidate method.

Disadvantage: When push enters the page of another controller, the page will also call the invalidate method of the timer, causing the timer invalid.

2, in willMoveToParentViewController processing

In willMoveToParentViewController method, to invalidate the timer do operation. If the container controller is outside the controller, this method will be called when we enter the controller and return to the previous controller. When we return to the previous controller, we will call the invalidate method of the timer.

- (void)willMoveToParentViewController:(UIViewController *)parent { if (self.viewLoaded) { if (_timer.valid) { [_timer invalidate]; _timer = nil; }}}Copy the code

Disadvantage: This method can only be used if the controller is surrounded by container controllers, such as UINavigationController.

3. Set the timer target to the intermediary

Using the mediator, point the timer’s target at the mediator. When the timer invokes the mediator’s response method, we have the controller actually respond to the method through a message forwarding mechanism. In this way, the controller strongly references the timer, the timer strongly references the mediator, and the mediator weakly references the controller. The controller can be released normally and then calls the timer’s invalidate method in the controller’s dealloc.

Intermediaries can inherit from NSObject or NSProxy. When inheriting from NSObject, if you can’t find a method to call in the current class, you go through the entire message lookup and forward process. However, when inheriting from NSProxy, it directly triggers the forwarding process, eliminating the process of finding methods from the parent class and dynamic method resolution, which is more efficient than using NSObject.

ViewController.m:

_timer = [NSTimer scheduledTimerWithTimeInterval:1 target:[TTRealProxy proxyWithTarget:self] selector:@selector(timerFire) userInfo:nil repeats:YES];

- (void)dealloc {
    [_timer invalidate];
}
Copy the code

TTRealProxy.h:

@interface TTRealProxy: NSProxy // weak reference @property (nonatomic, weak) id target; + (id)proxyWithTarget:(id)target; @endCopy the code

TTRealProxy.m:

@implementation TTRealProxy + (id)proxyWithTarget:(id)target { TTRealProxy *proxy = [TTRealProxy alloc]; proxy.target = target; return proxy; } //// - (void)timerFire {// NSLog(@"timerFire"); //} - (NSMethodSignature *)methodSignatureForSelector:(SEL)sel { return [_target methodSignatureForSelector:sel]; } - (void)forwardInvocation:(NSInvocation *)invocation { [invocation invokeWithTarget:_target]; } @endCopy the code

Disadvantages: more tedious.

4. Set the timer target to the class object

Point the target of the timer to an NSTimer object. Class objects don’t have to worry about references and releases. Since there is no strong reference to the controller, the controller can be released normally and the timer’s invalidate method is called in the controller’s dealloc method.

Refer to the implementation of YYKit:

// NSTimer+YYAdd.m + (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)seconds block:(void (^)(NSTimer *timer))block repeats:(BOOL)repeats { return [NSTimer scheduledTimerWithTimeInterval:seconds target:self selector:@selector(_yy_ExecBlock:) userInfo:[block copy] repeats:repeats]; } + (void)_yy_ExecBlock:(NSTimer *)timer { if ([timer userInfo]) { void (^block)(NSTimer *timer) = (void (^)(NSTimer *timer))[timer userInfo]; block(timer); }}Copy the code

5. Use the API with blocks

Use the API with blocks. Without using target, there is no need to reference the controller.

Cons: only NStimer has an API with blocks, CADisplayLink does not.

conclusion

To sum up, the fourth and fifth are the best solutions.