一、原理分析
平时我们使用NSTimer定时器可以说是非常的多了,但是我们可能会忽略一点,那就是NSTimer可能会导致循环引用。到底是怎么回事呢?
声明:并非所有的NSTimer会导致循环引用,下面两种情况除外
1、非repeat类型的。非repeat类型的timer不会强引用target,因此不会出现循环引用。
2、block类型的,新api。iOS 10之后才支持,因此对于还要支持老版本的app来说,这个API暂时无法使用。当然,block内部的循环引用也要避免。
请听我细说:
a.NSTimer创建方法有下面多种:(重点讲解下面这种)
- (NSTimer*)scheduledTimerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullableid)userInfo repeats:(BOOL)yesOrNo;
使用这种方式创建的NSTimer,会默认加入到runloop中,同时会与调用对象之间产生循环引用指针。
3.解决方案代码示例
@interface ViewController ()
@property (nonatomic, weak) NSTimer *timer;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(printstr) userInfo:nil repeats:YES];
}
- (void)printstr{
NSLog(@"ViewController");
}
- (void)dealloc{
NSLog(@"ViewController---dealloc");
[self.timer invalidate];
self.timer = nil;
NSLog(@"%@",self.timer);
}
@end
虽然使用的是weak关键字进行修饰,但是还是页面关闭后,还是会调用printstr方法。不信你试试看。这其中原因请看scheduledTimerWithTimeInterval官方文档解释。
image.png
知道原因了吧,尽管你声明为weak,但是NSTimer内部还是会把target进行强引用。
二、那么解决呢?
目前网上有很多的解决方案
1 比如用block(我看了,可能还是会有点问题)
2 用类别,创建NSTimer的类别(这种方式拓展性不强)
3 用NSProxy代理对象解决(目前我觉得这种比较靠谱)
@interface TimerTargetProxy : NSProxy
@property (nullable, nonatomic, weak, readonly) id target;
+ (instancetype)proxyWithTarget:(id)target;
@end
#import "TimerTargetProxy.h"
@interface TimerTargetProxy ()
- (instancetype)initWithTarget:(id)target;
@end
@implementation TimerTargetProxy
- (instancetype)initWithTarget:(id)target{
_target = target;
return self;
}
+ (instancetype)proxyWithTarget:(id)target{
return [[TimerTargetProxy alloc] initWithTarget:target];
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel{
if ([self.target respondsToSelector:sel]) {
return [self.target methodSignatureForSelector:sel];
}
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
- (void)forwardInvocation:(NSInvocation *)invocation{
SEL sel = [invocation selector];
if ([self.target respondsToSelector:sel]) {
[invocation invokeWithTarget:self.target];
}else{
[invocation setSelector:@selector(notfind)];
[invocation invokeWithTarget:self];
}
}
- (BOOL)respondsToSelector:(SEL)aSelector{
return [self.target respondsToSelector:aSelector];
}
- (void)notfind{
NSLog(@"notfind");
}
- (void)dealloc{
NSLog(@"TimerTargetProxy---dealloc");
}
@end
调用的话 将Controller中的timer创建方法中的target换一下就行了,如下:
self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:
[TimerTargetProxy proxyWithTarget:self] selector:@selector(printstr)
userInfo:nil repeats:YES];
然后运行,关闭页面,你看看是不是可以正常的杀掉timer。
over!
觉得我写得对你有帮助的或者改正的建议都可以联系我[工作邮箱]xiaobingli92@163.com