iOS - NSTimer循环引用

简介: iOS - NSTimer循环引用

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强引用NSTimerNSTimer强引用CCTimer,避免与ViewController强引用, 在ViewControllerdealloc方法中释放timerNSTimer进行销毁,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

相关文章
|
iOS开发
iOS循环引用深入理解
循环引用也就是引用之后造成的一个循环用图理解吧
239 0
iOS循环引用深入理解
|
API iOS开发
iOS开发 - NSTimer极限使用(二)
iOS开发 - NSTimer极限使用
212 0
|
API iOS开发
iOS开发 - NSTimer极限使用(一)
iOS开发 - NSTimer极限使用
279 0
|
C++ iOS开发
iOS - Block 循环引用
我们知道,循环引用即: 当两个对象A和B, 分别强引用对方,那么就会产生循环引用。即A释放的时候必须先释放B,而B释放的时候必须释放A。导致谁也不能释放 而打破循环引用的方法就是其中一方弱引用另一方
|
编解码 iOS开发
iOS - 循环引用
iOS - 循环引用
|
iOS开发
iOS NSTimer 定时器用法总结
iOS NSTimer 定时器用法总结
254 0
|
iOS开发 Serverless
iOS - OC NSTimer 定时器
前言 @interface NSTimer : NSObject @interface CADisplayLink : NSObject 作用 在指定的时间执行指定的任务。
1362 0
|
iOS开发 Swift
iOS - Swift NSTimer 定时器
前言 public class NSTimer : NSObject 作用 在指定的时间执行指定的任务。 每隔一段时间执行指定的任务。 1、定时器的创建 当定时器创建完(不用 scheduled 的,添加到 runloop 中)后,该定时器将在初始化时指定的 ti 秒后自动触发。
2078 0