NSTimer是我们常用的对象。但如果使用不当就有可能出现循环引用,造成内测泄漏。
计时器要和运行循环相关联,运行循环到时候会触发任务的执行。
NSTimer的创建可以预先安排在当前的运行循环中,也可以先创建好,然后由开发者自己来调用。不管使用哪种方式,都只有将NSTimer对象放到运行循环中才能使其正常工作。
例如以如下方法,我们创建一个NSTimer.
self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(timerFired:) userInfo:nil repeats:YES];
如果我们这样使用NSTimer,就很有可能造成循环引用所导致的内测泄漏。
1.原因分析:
timer是一个由当前self对象强引用的属性。timer对象的target参数是self,这时timer就对self对象产生了强引用。
这个方法对target参数是这样解释的
target:The object to which to send the message specified by aSelector when the timer fires. The timer maintains a strong reference to target until it (the timer) is invalidated.
所以如果我们想要释放掉timer可以调用invalidated方法。但是如果放到dealloc中,该timer依然释放不掉,因为这个dealloc方法根本不会执行。这是由于timer对self强引用,导致self无法释放,自然dealloc方法就没法调用。
2.解决办法
1)狭隘的做法
如果当前的self是一个控制器的话,那么我们有可能会在
dealloc方法中调用timer的invalidated方法。但是这样依然无法导致timer的失效,从而导致定时器和控制器都不能释放。所以靠谱一点的做法是放在viewWillDisappear中
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
if ([self.timer isValid]) {
[self.timer invalidate];
}
}
这样真的可以解决问题。但是这样真的好吗?有通用性吗,如果当前的self不是控制器对象呢,那你的invalidate方法放在什么地方进行调用呢。
2)方案二:给NSTimer增加一个分类
核心代码:
@interface NSTimer (YCClass)
+ (NSTimer *)yc_scheduledTimerWithTimeInterval:(NSTimeInterval)interval
block:(void(^)())block
repeats:(BOOL)repeats;
@end
@implementation NSTimer (YCClass)
+ (NSTimer *)yc_scheduledTimerWithTimeInterval:(NSTimeInterval)interval
block:(void(^)())block
repeats:(BOOL)repeats
{
return [self scheduledTimerWithTimeInterval:interval
target:self
selector:@selector(yc_blockInvoke:)
userInfo:[block copy]
repeats:repeats];
}
+ (void)yc_blockInvoke:(NSTimer *)timer {
void (^block)() = timer.userinfo;
if(block) {
block();
}
}
@end
分析:现在的target对象是NSTimer类对象,是一个单例。此处依然有自我形成的环,但是类对象无需回收,所以不用担心内存泄露的问题。
3)方案三:新增一个类对self进行弱引用
这是从YYKit中看到的解决方案,觉得很不错,现引用如下:
#import "YYWeakProxy.h"
@implementation YYWeakProxy
- (instancetype)initWithTarget:(id)target {
_target = target;
return self;
}
+ (instancetype)proxyWithTarget:(id)target {
return [[YYWeakProxy alloc] initWithTarget:target];
}
- (id)forwardingTargetForSelector:(SEL)selector {
return _target;
}
- (void)forwardInvocation:(NSInvocation *)invocation {
void *null = NULL;
[invocation setReturnValue:&null];
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)selector {
return [NSObject instanceMethodSignatureForSelector:@selector(init)];
}
- (BOOL)respondsToSelector:(SEL)aSelector {
return [_target respondsToSelector:aSelector];
}
- (BOOL)isEqual:(id)object {
return [_target isEqual:object];
}
- (NSUInteger)hash {
return [_target hash];
}
- (Class)superclass {
return [_target superclass];
}
- (Class)class {
return [_target class];
}
- (BOOL)isKindOfClass:(Class)aClass {
return [_target isKindOfClass:aClass];
}
- (BOOL)isMemberOfClass:(Class)aClass {
return [_target isMemberOfClass:aClass];
}
- (BOOL)conformsToProtocol:(Protocol *)aProtocol {
return [_target conformsToProtocol:aProtocol];
}
- (BOOL)isProxy {
return YES;
}
- (NSString *)description {
return [_target description];
}
- (NSString *)debugDescription {
return [_target debugDescription];
}
他的.h文件是这样的
@interface YYWeakProxy : NSProxy
@property (nullable, nonatomic, weak, readonly) id target;
- (instancetype)initWithTarget:(id)target;
+ (instancetype)proxyWithTarget:(id)target;
@end
所以我们在使用定时器的时候就很简单
YYWeakProxy *weakProxy = [YYWeakProxy proxyWithTarget:self];
self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0
target:weakProxy
selector:@selector(timerFired:) userInfo:nil
repeats:YES];
分析:该方法主要让timer强引用YYWeakProxy对象,但是该对象却对self对象弱引用,并将timerFired方法转发给self对象。这样当外界不对self强引用的时候,self就会被销毁掉,这样self所强引用的timer对象因为没有外界的强引用也会被销毁。