美文网首页iOS 底层原理
关于NSTimer循环引用问题的解决方案

关于NSTimer循环引用问题的解决方案

作者: 李Mr | 来源:发表于2018-06-19 16:43 被阅读63次

一、原理分析

平时我们使用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

相关文章

网友评论

    本文标题:关于NSTimer循环引用问题的解决方案

    本文链接:https://www.haomeiwen.com/subject/tkaaeftx.html