NSTimer的循环引用问题

本文介绍了如何解决使用NSTimer时产生的循环引用问题,通过两种方法:利用block和NSProxy实现弱引用,避免内存泄漏。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

为什么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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值