NSTimer 循环引用分析与解决方案

NSTimer循环引用分析

下面的方法可以创建计时器,并将其预先安排到当前运行循环(Run Loop)当中:

+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget 
selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo;

参数target和selector表示计时器将在哪个对象上调用哪个方法,repeats表示是否重复执行任务。
计时器会保留其目标对象,等到自身“失效”时再释放此对象。
(1)当repeats设置为NO时,执行完相关任务之后,计时器会自动失效;
(2)当调用invalidate方法时,可以令计时器失效;
因此将计时器设置成重复模式时,很容易导致“循环引用”的问题,必须自己调用invalidate方法,才能停止计时器。

NSTimer循环解决方案

 第一种方法

- (void)viewDidLoad {
    [super viewDidLoad];
    _timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(startTimer) userInfo:nil repeats:YES];
}

-(void)startTimer{
    NSLog(@"timer ok");
}

#pragma mark 第一种方法:在合适时机
- (void)didMoveToParentViewController:(UIViewController *)parent{
    if (parent == nil) {
        [_timer invalidate];
        _timer = nil;
    }
}

-(void)dealloc{
    NSLog(@"%@ dealloc",[self class]);  
}

 第二种方法(消息转发机制)

@interface ViewController2 ()

@property (nonatomic,strong) NSTimer  *timer ;
@property (nonatomic,strong) NSObject  *target ;

@end

@implementation ViewController2

-(void)dealloc{
    NSLog(@"%@ dealloc",[self class]);
    [_timer invalidate];
    _timer = nil;

}

- (void)viewDidLoad {
    [super viewDidLoad];
    _target = [NSObject new];
    class_addMethod([_target class], @selector(startTimer), class_getMethodImplementation([self class], @selector(startTimer)), "v@:");
    _timer = [NSTimer scheduledTimerWithTimeInterval:1 target:_target selector:@selector(startTimer) userInfo:nil repeats:YES];
}

-(void)startTimer{
    NSLog(@"timer ok");
}

@end

第三种方法(NSProxy)

@interface MyProxy : NSProxy

@property(nonatomic,weak) id target;

@end

@implementation MyProxy

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector { 
    return [self.target methodSignatureForSelector:aSelector];
}
- (void)forwardInvocation:(NSInvocation *)invocation {
    
    if ([self.target respondsToSelector:invocation.selector]) {
        [invocation invokeWithTarget:self.target];
    }
}

@end


#import "MyProxy.h"

@interface ViewController2 ()

@property (nonatomic,strong) NSTimer  *timer ;
@property (nonatomic,strong) MyProxy  *targetProxy ;

@end

@implementation ViewController2

-(void)dealloc{
    NSLog(@"%@ dealloc",[self class]);
    [_timer invalidate];
    _timer = nil;

}

- (void)viewDidLoad {
    [super viewDidLoad];
    _targetProxy = [MyProxy alloc];
    _targetProxy.target = self;
    _timer = [NSTimer scheduledTimerWithTimeInterval:1 target:_targetProxy selector:@selector(startTimer) userInfo:nil repeats:YES];
}

-(void)startTimer{
    NSLog(@"timer ok");
}
 

@end

第四种方法(苹果API接口解决方案(iOS 10.0以上))

在iOS 10.0以后,苹果官方新增了关于NSTimer的三个API:

+ (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));

+ (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));

- (instancetype)initWithFireDate:(NSDate *)date interval:(NSTimeInterval)interval repeats:(BOOL)repeats block:(void (^)(NSTimer *timer))block API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0));

这三个方法都有一个Block的回调方法。关于block参数,官方文档有说明:

the timer itself is passed as the parameter to this block when 
executed to aid in avoiding cyclical references。

翻译过来就是说,定时器在执行时,将自身作为参数传递给block,来帮助避免循环引用。
使用很简单,就不再举例了,使用时注意两点:

  1. 避免block的循环引用(使用__weak__strong来避免);
  2. 在持用NSTimer对象的类的方法中-(void)dealloc调用NSTimer 的- (void)invalidate方法;

猜你喜欢

转载自blog.csdn.net/LIN1986LIN/article/details/86647070