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,来帮助避免循环引用。
使用很简单,就不再举例了,使用时注意两点:
- 避免block的循环引用(使用
__weak
和__strong
来避免);- 在持用NSTimer对象的类的方法中
-(void)dealloc
调用NSTimer 的- (void)invalidate
方法;