1. NSTimer循环引用原因
如下代码,dealloc永远不会走,因为self引用timer,timer引用了target(self = BlockViewController2),造成了相互强引用
@interface BlockViewController2 () @property (nonatomic, strong) NSTimer *timer; @end @implementation BlockViewController2 - (void)viewDidLoad { [super viewDidLoad]; self.timer = [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(onTimer) userInfo:nil repeats:YES]; } - (void)onTimer { NSLog(@"-- timer --"); } - (void)dealloc { NSLog(@"dealloc 2 = %@", self); [self.timer invalidate]; } @end
2. 解决方案
2.1 主动调用释放timer(viewDidDisappear)
- (void)viewDidDisappear:(BOOL)animated { [super viewDidDisappear:animated]; [self.timer invalidate]; self.timer = nil; }
这个方法能解决循环引用问题,但可能导致新问题,比如在A vc 使用timer,在A push B的时候,释放timer,此时从b pop 回A,A timer已经不存在,需要重新创建
2.2 使用Timer的Block API
/// 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 chooses 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), 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 chooses 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));
self.timer = [NSTimer timerWithTimeInterval:1.0 repeats:YES block:^(NSTimer * _Nonnull timer) { NSLog(@"-- timer -- "); }]; [[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSDefaultRunLoopMode];
self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 repeats:YES block:^(NSTimer * _Nonnull timer) { NSLog(@"-- timer -- "); }];
使用这两个方法的时候需要注意避免Block的循环引用
2.3 将NSTimer做一层封装
因为NSTimer循环引用的核心问题是timer 的target引用了self,我们对target换成封装类的对象,这样可以解决timer引用self的问题
CCTimer.h
@interface CCTimer : NSObject - (void)startTimer; - (void)stopTimer; @end
CCTimer.m
#import "CCTimer.h" @implementation CCTimer { NSTimer * _timer; } - (void)startTimer { _timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@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 -- "); } @end
调用:
- (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]; }
这样的方式主要是让CCTimer
强引用NSTimer
, NSTimer
强引用CCTimer
,避免与ViewController强引用, 在ViewController
dealloc
方法中释放timer
对NSTimer
进行销毁,CCTimer
也就会相应的销毁,避免了循环引用
2.4 使用NSProxy解决
@interface CCProxy : NSProxy - (instancetype)initWithObject:(id)obj; + (instancetype)proxyWithObject:(id)obj; @end
// // 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]; } @end
调用:
CCProxy *proxy = [[CCProxy alloc] initWithObject:self]; self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:proxy selector:@selector(onTimer) userInfo:nil repeats:YES];
通过CCProxy这个伪基类(相当于ViewContoller的复制类),避免让timer引用ViewController