1. NSTimer循环引用问题
这是一个老生老生常谈的一个问题,但是其中还是有一些细节问题我觉得值得去写篇文章记录一下,其实这个问题是一个很有意思的问题。
- 首先提问1.NSTImer的循环引用是timer 的拥有者A和timer的target A 造成的么?即timer=>self=>timer
事实上及时timer不做为A的属性,或者timer 的target 传入__ weak, A也不会释放。
原因在于,其实造成这种循环引用的根本原因并不是如此,timer内部会强引用target,即使传进来的是weak类型,造成这个问题其实是:runLoop=>timer=>target
2.NSTimer循环引用解决办法
要阻断这种循环引用我们就需要阻断runloop对timer的引用或者timer对target的强引用,但是timer的内部实现我们无法控制,而且timer为了防止调用target的action造成崩溃,肯定会对target进行强引用。
1> runloop=>timer=>proxy-->target
什么意思呢?
我们在timer和target中间添加一个模型,让timer的target为它,它弱引用占有需要重复执行方法的对象。proxy 采用消息转发的技术,将消息转发给需要定时重复执行方法的对象。还是拿代码来看一下吧。
/// 类声明
@interface TimerProxy : NSObject
@property (nonatomic, weak) id target;
+ (instancetype)_timerProxyWithTarget:(id)target;
@end
/// 类实现
@implementation TimerProxy
+ (instancetype)timerProxyWithTarget:(id)target {
TimerProxy *timerProxy = [[TimerProxy alloc] init];
timerProxy.target = target;
return timerProxy;
}
/// 消息转发
/// 消息转发第一步:询问是否本类动态的实现了此方法,我们可以动态的添加实现的方法
/// 第二步:询问转发给某个对象处理此方法
/// 第三步:通过方法签名,生成一个NSInvocation对象,执行方法
/// 我们主要用到了后两步,把方法转发给本类target去执行,如果target不存在,就会进入到消息转发的第三步,我们执行NSObject的 init方法
- (id)forwardingTargetForSelector:(SEL)aSelector {
return self.target;
}
- (void)forwardInvocation:(NSInvocation *)anInvocation {
void *nullValue = NULL;
[anInvocation setReturnValue:&nullValue];
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
return [NSObject instanceMethodSignatureForSelector:@selector(init)];
}
-(void)dealloc {
NSLog(@"_TimerProxy 释放了");
}
@end
/// 应用
NSTimer *testTimer = [NSTimer scheduledTimerWithTimeInterval:1 target:[TimerProxy timerProxyWithTarget:self] selector:@selector(excuteSomething) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:testTimer forMode:NSRunLoopCommonModes];
_timer = testTimer;
/// 需要在释放的时候,移除timer,否则proxy和timer之间循环引用
- (void)dealloc {
NSLog(@"TestTimerObj释放了");
[_timer invalidate];
_timer = nil;
}
- 采用block的方式,runloop=>timer=>timerSelf=>block
添加分类方法,采用timer将自己作为target,执行自己的一个方法,block作为userInfo传递。
@interface NSTimer (TestWeakly)
+ (instancetype)weaklyTimerWithTimerInterval:(NSTimeInterval)timerInterval block:(void(^)(void))excuteBlock repeat:(BOOL)repeat;
@end
@implementation NSTimer (TestWeakly)
/// 添加一个可执行
+ (instancetype)weaklyTimerWithTimerInterval:(NSTimeInterval)timerInterval block:(void (^)(void))executeBlock repeat:(BOOL)repeat {
return [self scheduledTimerWithTimeInterval:timerInterval target:self selector:@selector(weaklyBlockInvoke:) userInfo:[executeBlock copy] repeats:repeat];
}
+ (void)weaklyBlockInvoke:(NSTimer *)timer {
void(^block)(void) = timer.userInfo;
if (block) {
block();
}
}
@end
/// 应用
/// 注意executeBlock内部引用self采用__weak
__weak typeof(self) weakself = self;
NSTimer *testTimer = [NSTimer weaklyTimerWithTimerInterval:1 block:^{
__strong typeof(weakself) strongself = weakself;
[strongself excuteSomething];
} repeat:YES];
[[NSRunLoop currentRunLoop] addTimer:testTimer forMode:NSRunLoopCommonModes];
_timer = testTimer;
/// 销毁时,需要停止销毁timer
- (void)dealloc {
NSLog(@"TestTimerObj释放了");
[_timer invalidate];
_timer = nil;
}
- 采用GCD的timer
/// 注意block的循环引用,造成内存泄漏
dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue());
dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC, 0 * NSEC_PER_SEC);
dispatch_source_set_event_handler(timer, ^{
[weakself excuteSomething];
});
dispatch_resume(timer);
_gcdTimer = timer;
/// 释放GCDtimer
- (void)dealloc {
NSLog(@"TestTimerObj释放了");
dispatch_source_cancel(_gcdTimer);
}
上边就是NSTimer,循环引用,造成内存泄漏的原因。以及三种解决办法