1. NSTimer circular reference reason

The following code, dealloc will never go, because self references timer, and timer references target(self = BlockViewController2), resulting in strong references

@interface BlockViewController2 () @property (nonatomic, strong) NSTimer *timer; @end @implementation BlockViewController2 - (void)viewDidLoad { [super viewDidLoad]; The self. The timer = [NSTimer scheduledTimerWithTimeInterval: 2.0 target: self selector: @ the selector (onTimer) the userInfo: nil repeats:YES]; } - (void)onTimer { NSLog(@"-- timer --"); } - (void)dealloc { NSLog(@"dealloc 2 = %@", self); [self.timer invalidate]; } @endCopy the code

2. Solutions

2.1 Active Call Release Timer (viewDidDisappear)

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

This method can solve the problem of circular reference, but may lead to new problems. For example, when A VC uses timer, when A pushes B, the timer is released. At this time, B pops back to A, and A timer does not exist and needs to be created again

2.2 Using the Block API of Timer

/// Creates and returns a new NSTimer object initialized with the specified block object. This timer needs to be scheduled on a run loop (via -[NSRunLoop addTimer:]) before it will fire. /// - parameter: TimeInterval The number of seconds between firings of The timer. If seconds is less than or equal to 0.0, This method puts the nonnegative value of 0.1 milliseconds instead /// -parameter: repeats If YES, the timer will repeatedly reschedule itself until invalidated. If NO, the timer will be invalidated after it fires. /// - parameter: block The execution body of the timer; the timer itself is passed as the parameter to this block when executed to aid in avoiding cyclical references + (NSTimer *)timerWithTimeInterval:(NSTimeInterval)interval repeats:(BOOL)repeats block:(void (^)(NSTimer *timer))block API_AVAILABLE (macosx (10.12), the ios (10.0), watchos (3.0), tvos (10.0)); /// Creates and returns a new NSTimer object initialized with the specified block object and schedules it on the current  run loop in the default mode. /// - parameter: Ti The number of seconds between firings of The timer. If seconds is less than or equal to 0.0, This method puts the nonnegative value of 0.1 milliseconds instead /// -parameter: repeats If YES, the timer will repeatedly reschedule itself until invalidated. If NO, the timer will be invalidated after it fires. /// - parameter: block The execution body of the timer; the timer itself is passed as the parameter to this block when executed to aid in avoiding cyclical references + (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)interval repeats:(BOOL)repeats block:(void (^)(NSTimer * Timer))block API_AVAILABLE(MacOSx (10.12), ios(10.0), Watchos (3.0), TVOs (10.0));Copy the code
The self. The timer = [NSTimer timerWithTimeInterval: 1.0 repeats: YES block: ^ (NSTimer * _Nonnull timer) {NSLog (@ "- timer -"); }]; [[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSDefaultRunLoopMode];Copy the code
The self. The timer = [NSTimer scheduledTimerWithTimeInterval: 1.0 repeats: YES block: ^ (NSTimer * _Nonnull timer) {NSLog (@ "- timer -- "); }];Copy the code

When using these two methods, you need to be careful to avoid circular references to blocks

2.3 Encapsulate nes Timer

Because the core problem with the NSTimer loop reference is that the timer target refers to self, we can solve the timer reference self problem by replacing target with an object that encapsulates the class

CCTimer.h

@interface CCTimer : NSObject

- (void)startTimer;

- (void)stopTimer;

@end
Copy the code

CCTimer.m

#import "CCTimer.h" @implementation CCTimer { NSTimer * _timer; } - (void) startTimer {_timer = [NSTimer scheduledTimerWithTimeInterval: 1.0 target: self selector: @ the selector (onTimer) userInfo:nil repeats:YES]; } - (void)stopTimer { if (_timer == nil) { return; } [_timer invalidate]; _timer = nil; } - (void)onTimer { NSLog(@"-- timer --"); } - (void)dealloc { NSLog(@"-- timer dealloc -- "); } @endCopy the code

Call:

- (void)viewDidLoad {
    [super viewDidLoad];
    self.ccTimer = [[CCTimer alloc] init];
    [self.ccTimer startTimer];
}

- (void)onTimer {
    NSLog(@"-- timer --");
}

- (void)dealloc {
    NSLog(@"dealloc 2 = %@", self);
    [self.ccTimer stopTimer];
    [self.timer invalidate];
}
Copy the code

So basically, CCTimer strongly references NSTimer, NSTimer strongly references CCTimer, avoids strongly referencing the ViewController, releases the timer in the ViewController dealloc method and destroys the NSTimer, Cctimers are destroyed accordingly, avoiding circular references

2.4 Using NSProxy

@interface CCProxy : NSProxy

- (instancetype)initWithObject:(id)obj;

+ (instancetype)proxyWithObject:(id)obj;

@end
Copy the code
// // CCProxy.m // MemoryManageDemo // // Created by Ternence on 2021/5/17. // #import "CCProxy.h" @interface CCProxy ()  @property (nonatomic, weak) id obj; @end @implementation CCProxy - (instancetype)initWithObject:(id)obj { self.obj = obj; return self; } + (instancetype)proxyWithObject:(id)obj { return [[self alloc] initWithObject:obj]; } - (void)forwardInvocation:(NSInvocation *)invocation { if ([self.obj respondsToSelector:invocation.selector]) { [invocation invokeWithTarget: self.obj]; } } - (NSMethodSignature *)methodSignatureForSelector:(SEL)sel { return [self.obj methodSignatureForSelector:sel]; } @endCopy the code

Call:

CCProxy *proxy = [[CCProxy alloc] initWithObject:self]; The self. The timer = [NSTimer scheduledTimerWithTimeInterval: 1.0 target: proxy selector: @ the selector (onTimer) the userInfo: nil repeats:YES];Copy the code

By using CCProxy, a pseudo-base class (equivalent to a duplicate class of ViewContoller), the Timer is prevented from referencing the ViewController