排除NSTimer对目标对象的保留

来源:互联网 发布:程序员自我评价幽默 编辑:程序博客网 时间:2024/04/28 17:50

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对象因为没有外界的强引用也会被销毁。

0 0
原创粉丝点击