为什么NSTimer会产生循环引用的问题
`_timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(countDown) userInfo:nil repeats:YES];`
@property (nonatomic,strong) NSTimer *timer;
第一句中target:self 强引用了self 第二句中self 把timer做为属性也强引用了,两者相互持有,都不会释放,自然self也不会走dealloc方法,无法在dealloc中invalidate。 之前的做法是在viewWillDisappear中调用[timer invalidate];但这样做当self消失的时候timer就停止了,如果现在的需求是不让timer停止呢?
第一种方法如下所示:
@interface NSTimer (JQUsingBlock)
+ (NSTimer *)jq_scheduledTimerWithTimeInterval:(NSTimeInterval)ti
block:(void(^)())block
repeats:(BOOL)repeats;
@end
@implementation NSTimer (JQUsingBlock)
+ (NSTimer *)lsz_scheduledTimerWithTimeInterval:(NSTimeInterval)ti
block:(void(^)())block
repeats:(BOOL)repeats{
return [self scheduledTimerWithTimeInterval:ti
target:self
selector:@selector(jq_blockInvoke:)
userInfo:[block copy]
repeats:repeats];
}
+ (void)jq_blockInvoke:(NSTimer *)timer{
void(^block)() = timer.userInfo;
if (block) {
block();
}
}
@end
定义一个NSTimer
的类别,在类别中定义一个类方法。类方法有一个类型为块的参数(定义的块位于栈上,为了防止块被释放,需要调用copy
方法,将块移到堆上)。使用这个类别的方式如下:
__weak ViewController *weakSelf = self;
_timer = [NSTimer lsz_scheduledTimerWithTimeInterval:1.0
block:^{
__strong ViewController *strongSelf = weakSelf;
[strongSelf startCounting];
}
repeats:YES];
使用这种方案就可以防止NSTimer
对类的保留,从而打破了循环引用的产生。__strong ViewController *strongSelf = weakSelf
主要是为了防止执行块的代码时,类被释放了。在类的dealloc
方法中,记得调用[_timer invalidate]
。
第二种方法:使用NSProxy
NSProxy本身是一个抽象类,它遵循NSObject协议,提供了消息转发的通用接口。NSProxy通常用来实现消息转发机制和惰性初始化资源。
使用NSProxy,你需要写一个子类继承它,然后需要实现init以及消息转发的相关方法。
1 //当一个消息转发的动作NSInvocation到来的时候,在这里选择把消息转发给对应的实际处理对象 2 - (void)forwardInvocation:(NSInvocation *)anInvocation 3 4 //当一个SEL到来的时候,在这里返回SEL对应的NSMethodSignature 5 - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector 6 7 //是否响应一个SEL 8 + (BOOL)respondsToSelector:(SEL)aSelector
消息转发机制
消息转发涉及到三个核心方法
1 //消息转发第一步,在这里可以动态的为类添加方法,这样类自己就能处理了 2 +resolveInstanceMethod: 3 //消息转发第二步,在第一步无法完成的情况下执行。这里只是把一个Selector简单的转发给另一个对象 4 - forwardingTargetForSelector: 5 //消息转发第三步,在第二步也无法完成的情况下执行。将整个消息封装成NSInvocation,传递下去 6 - forwardInvocation:
消息转发机制使得代码变的很灵活:一个类本身可以完全不实现某些方法,它只要能转发就可以了。
WeakProxy来实现弱引用
@interface WeakProxy : NSProxy @property (weak,nonatomic,readonly)id target; + (instancetype)proxyWithTarget:(id)target; - (instancetype)initWithTarget:(id)target; @end @implementation WeakProxy - (instancetype)initWithTarget:(id)target{ _target = target; return self; } + (instancetype)proxyWithTarget:(id)target{ return [[self alloc] initWithTarget:target]; } - (void)forwardInvocation:(NSInvocation *)invocation{ SEL sel = [invocation selector]; if ([self.target respondsToSelector:sel]) { [invocation invokeWithTarget:self.target]; } } - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{ return [self.target methodSignatureForSelector:aSelector]; } - (BOOL)respondsToSelector:(SEL)aSelector{ return [self.target respondsToSelector:aSelector]; } @end
外部创建Timer
self.timer = [NSTimer timerWithTimeInterval:1 target:[WeakProxy proxyWithTarget:self] selector:@selector(timerInvoked:) userInfo:nil repeats:YES];
原理如下:
我们把虚线处变成了弱引用。于是,Controller就可以被释放掉,我们在Controller的dealloc中调用invalidate
,就断掉了Runloop对Timer的引用,于是整个三个淡蓝色的就都被释放掉了。
ios 10以后的做法
__weak SubViewController *weakSelf = self;
_timer = [NSTimer scheduledTimerWithTimeInterval:1.0 repeats:YES block:^(NSTimer * _Nonnull timer) {
[weakSelf countDown];
}];
iOS10以后出现了一个新的方法不需要传入self,在block里循环执行就可以了
参考文章:
https://www.cnblogs.com/H7N9/p/6540578.html
http://www.jianshu.com/p/1dbd7a228a22