First of all, there are two concepts:
Memory leak: A memory leak occurs when the memory allocated by the system is not reclaimed in time.
Memory overflow: Indicates that insufficient memory space is available during memory application, including stack overflow and heap overflow.
Here we go: First create a circular reference, create a TestViewController, create a timer,
_timer = [NSTimer scheduledTimerWithTimeInterval: 1.0 f target: self selector: @ the selector (the fire) the userInfo: nil repeats: YES]; -(void)fire{ NSLog(@"fire"); } -(void)dealloc { NSLog(@"%@ delloc",self); }Copy the code
If you run the above code and TestViewController pops back up, you’ll see that the fire keeps printing, causing a circular reference problem. Essentially, NSTimer is a strong reference to the current target (self), and they hold each other during the strong reference process. So there is no way for them to release normally. The destructor dealloc contains [_timer invalidate]. _timer = nil; In fact, when you run it, you’ll notice that the dealloc function doesn’t execute. As we all know, Dealloc is a system method in every controller, executed by the system response, and dealloc is executed when the current controller is destroyed, but the current class created by the circular reference is not destroyed.
Next, let’s solve the NSTimer circular reference problem:
1. The first method: Destroy NSTimer at the right time
- (void) didMoveToParentViewController: (the parent UIViewController *) {/ / parent = = nil when the parent view is empty (after iOS8.0 provide apis, used to manage the child views) to the life cycle of the if (! parent) { [_timer invalidate]; _timer = nil; }}Copy the code
2. The second way: Introduce the middleman
The current strong reference is passed to target, and if the current ViewController is properly reclaimed, the dealloc method will execute properly.
@property (nonatomic,strong) id target; // Create a target object, _target = [NSObject new]; For the current _target, which is essentially the handler of the message, obviously the _target needs a selector, so we dynamically add a method to the current object, class_addMethod([_target class], @selector(fire), class_getMethodImplementation([self class], @selector(fire)), "v@:"); _timer = [NSTimer scheduledTimerWithTimeInterval: 1.0 f target: _target selector: @ the selector (the fire) the userInfo: nil repeats: YES]; -(void)dealloc { NSLog(@"%@ delloc",self); [_timer invalidate]; _timer = nil; } The destructor of the viewController will execute normally.Copy the code
3. The third way: The advanced middleman
In this case, we need to use a virtual base class NSProxy (NSProxy is mainly used for message forwarding processing).
// HZProxy.h #import <Foundation/Foundation.h> @interface HZProxy : NSProxy // Still have a target@property (nonatomic,weak) id target; M #import "hzproxy. h" @implementation HZProxy -(NSMethodSignature *)methodSignatureForSelector:(SEL)sel { return [self.target methodSignatureForSelector:sel]; } / / specify the current message handler - (void) forwardInvocation: (NSInvocation *) invocation {[invocation invokeWithTarget: self. The target]; Alloc = [HZProxy alloc]; // Set the target of the current Proxy to the current self, since it is the current viewController that handles the message. _hzproxy. target = self; _timer = [NSTimer scheduledTimerWithTimeInterval: 1.0 f target: _hzProxy selector: @ the selector (the fire) the userInfo: nil repeats:YES]; The current _timer object is now handled by _hzProxy. Run it and you can see that the destructor of the ViewController is working fineCopy the code
4. The fourth type: Timer with block
When we created the timer, apple realized that the NSTimer API had some problems, so after iOS10.0 they provided a block method to solve the problem of NSTimer cyclic references.
__weak typeof(self) weakSelf = self; _timer = [NSTimer scheduledTimerWithTimeInterval: 1.0 f repeats: YES block: ^ (NSTimer * _Nonnull timer) {__strong typeof(self) strongSelf = weakSelf; [strongSelf fire]; }];Copy the code
But in order to be compatible with the current API prior to iOS10.0, we can HOOK it at this point
First of all, let's create a classification of NSTimer, // NSTimer+ZHTimer.h #import <Foundation/Foundation.h> @interface NSTimer (ZHTimer) +(NSTimer *)zh_scheduledTimerWithTimeInterval:(NSTimeInterval)interval repeats:(BOOL)repeats block:(void (^)(void))block; @end // NSTimer+ZHTimer.m #import "NSTimer+ZHTimer.h" @implementation NSTimer (ZHTimer) +(NSTimer *)zh_scheduledTimerWithTimeInterval:(NSTimeInterval)interval repeats:(BOOL)repeats block:(void (^)(void))block { return [self scheduledTimerWithTimeInterval:interval target:self selector:@selector(zh_blockHandle:) userInfo:block repeats:YES]; // The singleton is a single instance of the current class object, so every time we create an instance variable, the singleton is created by this class object, so we don't need to worry about the current target causing a circular reference, because the singleton doesn't need to be released. The singleton in memory is released only when the APP is Q. } +(void)zh_blockHandle:(NSTimer *)timer{ void(^block)(void) = timer.userInfo; if (block) { block(); }} @end // call __weak Typeof (self) weakSelf = self; _timer = [NSTimer zh_scheduledTimerWithTimeInterval: 1.0 f repeats: YES block: ^ {__strong typeof strongSelf = (self) weakSelf; [strongSelf fire]; }];Copy the code
The above is my personal understanding of NSTimer circular reference and processing methods, may not be very perfect, very deep, there are bugs, please point out, thank you very much.