nstimer循环引用_NSTimer循环引用的几种解决方案

NSTimer在使用中需要注意,避免循环引用的问题。但是需要纠正一种说法

注意:timer内存泄漏,部分童鞋认为是vc与timer循环引用造成的,这种说法是错误的!

@interface ViewController ()

@property (nonatomic, weak) NSTimer *timer;

@end

- (void)viewDidLoad {

[super viewDidLoad];

{

NSTimer *one = [NSTimer scheduledTimerWithTimeInterval:1.f target:self selector:@selector(tick:) userInfo:nil repeats:YES];

_timer = one;

}

}

timer强引用target,即timer强引用vc,然而vc并没有强引用timer,哪来的vc与timer循环引用?但是,如果vc没有强引用timer,timer是如何存活的?

其实,默认将timer加入到currentRunLoop中,currentRunLoop会强引用timer,而currentRunLoop就是mainRunLoop,mainRunLoop一直存活,所以timer可以存活

关系可参考如下图示:

image.png

从以上关系图可见,只要runLoop存活,vc必然存活,所以vc的dealloc方法自然就不会执行。因此,将timer的销毁方法放在dealloc中必然造成内存泄漏!

虽然NSRunLoop提供了addTimer接口,但是并没有提供removeTimer接口,显然,runLoop与timer这条线无法直接销毁,所以只能从vc与timer持有关系入手。

内部其实对target对象做了retain操作, _target = RETAIN(object);

- (id) initWithFireDate: (NSDate*)fd

interval: (NSTimeInterval)ti

target: (id)object

selector: (SEL)selector

userInfo: (id)info

repeats: (BOOL)f

{

if (ti <= 0.0)

{

ti = 0.0001;

}

if (fd == nil)

{

_date = [[NSDate_class allocWithZone: NSDefaultMallocZone()]

initWithTimeIntervalSinceNow: ti];

}

else

{

_date = [fd copyWithZone: NSDefaultMallocZone()];

}

_target = RETAIN(object);

_selector = selector;

_info = RETAIN(info);

if (f == YES)

{

_repeats = YES;

_interval = ti;

}

else

{

_repeats = NO;

_interval = 0.0;

}

return self;

}

调用invalidate的时候,会执行destroy操作

- (void) invalidate

{

/* OPENSTEP allows this method to be called multiple times. */

_invalidated = YES;

if (_target != nil)

{

DESTROY(_target);

}

if (_info != nil)

{

DESTROY(_info);

}

}

下面介绍一下几种解决timer循环引用的方法。

1. 选择合适的时机手动释放timer

调用[self.timer invalidate];

2. timer使用block方式添加Target-Action

这里我们需要自己在NSTimer的分类中添加类方法:

@interface NSTimer (YYAdd)

/**

Creates and returns a new NSTimer object and schedules it on the current run

loop in the default mode.

@discussion After seconds seconds have elapsed, the timer fires,

sending the message aSelector to target.

@param seconds The number of seconds between firings of the timer. If seconds

is less than or equal to 0.0, this method chooses the

nonnegative value of 0.1 milliseconds instead.

@param block The block to invoke when the timer fires. The timer maintains

a strong reference to the block until it (the timer) is invalidated.

@param repeats If YES, the timer will repeatedly reschedule itself until

invalidated. If NO, the timer will be invalidated after it fires.

@return A new NSTimer object, configured according to the specified parameters.

*/

+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)seconds block:(void (^)(NSTimer *timer))block repeats:(BOOL)repeats;

/**

Creates and returns a new NSTimer object initialized with the specified block.

@discussion You must add the new timer to a run loop, using addTimer:forMode:.

Then, after seconds have elapsed, the timer fires, invoking

block. (If the timer is configured to repeat, there is no need

to subsequently re-add the timer to the run loop.)

@param seconds The number of seconds between firings of the timer. If seconds

is less than or equal to 0.0, this method chooses the

nonnegative value of 0.1 milliseconds instead.

@param block The block to invoke when the timer fires. The timer instructs

the block to maintain a strong reference to its arguments.

@param repeats If YES, the timer will repeatedly reschedule itself until

invalidated. If NO, the timer will be invalidated after it fires.

@return A new NSTimer object, configured according to the specified parameters.

*/

+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)seconds block:(void (^)(NSTimer *timer))block repeats:(BOOL)repeats;

@end

通过block的方式,获取action,实际的target设置为self,即NSTimer类。这样我们在使用timer时,由于target的改变,就不再有循环引用了。 使用中还需要注意block可能引起的循环引用,所以使用weakSelf:

__weak typeof(self) weakSelf = self;

self.timer = [NSTimer scheduledTimerWithTimeInterval:1 block:^{

[weakSelf dosomething];

} repeats:YES];

#import "NSTimer+YYAdd.h"

#import "YYCategoriesMacro.h"

YYSYNTH_DUMMY_CLASS(NSTimer_YYAdd)

@implementation NSTimer (YYAdd)

+ (void)_yy_ExecBlock:(NSTimer *)timer {

if ([timer userInfo]) {

void (^block)(NSTimer *timer) = (void (^)(NSTimer *timer))[timer userInfo];

block(timer);

}

}

+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)seconds block:(void (^)(NSTimer *timer))block repeats:(BOOL)repeats {

return [NSTimer scheduledTimerWithTimeInterval:seconds target:self selector:@selector(_yy_ExecBlock:) userInfo:[block copy] repeats:repeats];

}

+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)seconds block:(void (^)(NSTimer *timer))block repeats:(BOOL)repeats {

return [NSTimer timerWithTimeInterval:seconds target:self selector:@selector(_yy_ExecBlock:) userInfo:[block copy] repeats:repeats];

}

@end

虽然没有了循环引用,但是还是应该记得在dealloc时释放timer。

3. 通过proxy转发的形式解决

如图:

image.png

考虑到循环引用的原因,改方案就是需要打破这些相互引用关系,因此添加一个中间件,弱引用self,同时timer引用了中间件,这样通过弱引用来解决了相互引用

proxy弱引用vc,所以vc可以释放,当vc执行dealloc,在dealloc内部销毁timer即可

参考:YYKit解决方案:

@interface YYWeakProxy : NSProxy

/**

The proxy target.

*/

@property (nullable, nonatomic, weak, readonly) id target;

/**

Creates a new weak proxy for target.

@param target Target object.

@return A new proxy object.

*/

- (instancetype)initWithTarget:(id)target;

/**

Creates a new weak proxy for target.

@param target Target object.

@return A new proxy object.

*/

+ (instancetype)proxyWithTarget:(id)target;

@end

#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];

}

@end

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值