NSTimer循环引用的几种解决方案
前言
在iOS中,NSTimer的使用是非常频繁的,但是NSTimer在使用中需要注意,避免循环引用的问题。之前经常这样写:
- (void)setupTimer {
self.timer = [NSTimer scheduledTimerWithTimeInterval: target:self selector:@selector(timerAction) userInfo:nil repeats:YES];
} - (void)dealloc {
[self.timer invalidate];
self.timer = nil;
}
由于self强引用了timer,同时timer也强引用了self,所以循环引用造成dealloc方法根本不会走,self和timer都不会被释放,造成内存泄漏。
下面介绍一下几种解决timer循环引用的方法。
1. 选择合适的时机手动释放timer(该方法并不太合理)
在之前自己就是这样解决循环引用的:
- 控制器中
- (void)viewDidDisappear:(BOOL)animated {
[super viewDidDisappear:animated]; [self.timer invalidate];
self.timer = nil;
}
- view中
- (void)removeFromSuperview {
[super removeFromSuperview]; [self.timer invalidate];
self.timer = nil;
}
在某些情况下,这种做法是可以解决问题的,但是有时却会引起其他问题,比如控制器push到下一个控制器,viewDidDisappear后,timer被释放,此时再回来,timer已经不复存在了。
所以,这种"方案"并不是合理的。
2. timer使用block方式添加Target-Action
这里我们需要自己在NSTimer的分类中添加类方法:
@implementation NSTimer (BlcokTimer) + (NSTimer *)bl_scheduledTimerWithTimeInterval:(NSTimeInterval)interval block:(void (^)(void))block repeats:(BOOL)repeats { return [self scheduledTimerWithTimeInterval:interval target:self selector:@selector(bl_blockSelector:) userInfo:[block copy] repeats:repeats];
} + (void)bl_blockSelector:(NSTimer *)timer { void(^block)(void) = timer.userInfo;
if (block) {
block();
}
}
@end
通过block的方式,获取action,实际的target设置为self,即NSTimer类。这样我们在使用timer时,由于target的改变,就不再有循环引用了。 使用中还需要注意block可能引起的循环引用,所以使用weakSelf:
__weak typeof(self) weakSelf = self;
self.timer = [NSTimer bl_scheduledTimerWithTimeInterval: block:^{
[weakSelf changeText];
} repeats:YES];
虽然没有了循环引用,但是还是应该记得在dealloc时释放timer。
3. 给self添加中间件proxy
考虑到循环引用的原因,改方案就是需要打破这些相互引用关系,因此添加一个中间件,弱引用self,同时timer引用了中间件,这样通过弱引用来解决了相互引用,如图:
接下来看看怎么实现这个中间件,直接上代码:
@interface ZYWeakObject()
@property (weak, nonatomic) id weakObject;
@end
@implementation ZYWeakObject
- (instancetype)initWithWeakObject:(id)obj {
_weakObject = obj;
return self;
}
+ (instancetype)proxyWithWeakObject:(id)obj {
return [[ZYWeakObject alloc] initWithWeakObject:obj];
}
@interface ZYWeakObject() @property (weak, nonatomic) id weakObject; @end @implementation ZYWeakObject - (instancetype)initWithWeakObject:(id)obj {
_weakObject = obj;
return self;
} + (instancetype)proxyWithWeakObject:(id)obj {
return [[ZYWeakObject alloc] initWithWeakObject:obj];
}
仅仅添加了weak类型的属性还不够,为了保证中间件能够响应外部self的事件,需要通过消息转发机制,让实际的响应target还是外部self,这一步至关重要,主要涉及到runtime的消息机制。
/**
* 消息转发,让_weakObject响应事件
*/
- (id)forwardingTargetForSelector:(SEL)aSelector {
return _weakObject;
} - (void)forwardInvocation:(NSInvocation *)invocation {
void *null = NULL;
[invocation setReturnValue:&null];
} - (BOOL)respondsToSelector:(SEL)aSelector {
return [_weakObject respondsToSelector:aSelector];
}
接下来就可以这样使用中间件了:
// target要设置成weakObj,实际响应事件的是self
ZYWeakObject *weakObj = [ZYWeakObject proxyWithWeakObject:self];
self.timer = [NSTimer scheduledTimerWithTimeInterval: target:weakObj selector:@selector(changeText) userInfo:nil repeats:YES];
结论
经测试,以上两种方案都是可以解决timer的循环引用问题
代码请移步github: Demo https://github.com/zhouyangyng/timerRetainCycle
NSTimer循环引用的几种解决方案的更多相关文章
- CADisplayLink、NSTimer循环引用解决方案
前言:CADisplayLink.NSTimer 循环引用问题 CADisplayLink.NSTimer会对Target产生强引用,如果target又对他们产生强引用,那么就会引发循环引用. @ ...
- iOS容易造成循环引用的三种场景
iOS容易造成循环引用的三种场景 ARC已经出来很久了,自动释放内存的确很方便,但是并非绝对安全绝对不会产生内存泄露.导致iOS对象无法按预期释放的一个无形杀手是--循环引用.循环引用可以简单理解为 ...
- 解决NSTimer循环引用Retain Cycle问题
解决NSTimer循环引用Retain Cycle问题 iOS开发中以下的情况会产生循环引用 block delegate NSTimer 循环引用导致一些对象无法销毁,一定的情况下会对我们横须造成影 ...
- 解决NSTimer循环引用
NSTimer常见用法 @interface XXClass : NSObject - (void)start; - (void)stop; @end @implementation XXClass ...
- 【转】iOS学习之容易造成循环引用的三种场景
ARC已经出来很久了,自动释放内存的确很方便,但是并非绝对安全绝对不会产生内存泄露.导致iOS对象无法按预期释放的一个无形杀手是——循环引用.循环引用可以简单理解为A引用了B,而B又引用了A,双方都同 ...
- 【原】iOS容易造成循环引用的三种场景,就在你我身边!
ARC已经出来很久了,自动释放内存的确很方便,但是并非绝对安全绝对不会产生内存泄露.导致iOS对象无法按预期释放的一个无形杀手是——循环引用.循环引用可以简单理解为A引用了B,而B又引用了A,双方都同 ...
- iOS-容易造成循环引用的三种场景
ARC已经出来很久了,自动释放内存的确很方便,但是并非绝对安全绝对不会产生内存泄露.导致iOS对象无法按预期释放的一个无形杀手是——循环引 用.循环引用可以简单理解为A引用了B,而B又引用了A,双方都 ...
- iOS 容易引“起循环引用”的三种场景
笔者在阅读中总结了一下,在iOS平台容易引起循环引用的四个场景: 一.parent-child相互持有.委托模式 [案例]: @interface FTAppCenterMainViewContr ...
- 用block解决nstimer循环引用
大多数开发者可能都会这样来实现定时器.创建定时器的时候,由于目标对象是self,所以要保留此实例.然而,因为定时器是用实例变量存放的,所以实例也保留了定时器,这就造成了循环引用.除非调用stop方法, ...
随机推荐
- Tiny4412之外部中断
一:外部中断 在之前我们学习按键驱动的时候,我们检测按键有没有按下是通过轮循的方式(也就是我们说的死循环),这样虽然可以检测实现按键,但太浪费系统资源了,不论我们按键中断有没有发生,cpu都要一直进行 ...
- Java中创建线程的三种方式及其优缺点
1.自定义一个继承Thread的类,由于Java的单继承特性,限制了该类的扩展性. 2.实现Runnable接口,重写run()方法. 3.实现Callable接口,重写call方法.线程执行体可以有 ...
- awk高级玩法
1. 程序元素 一个awk 程序是一对以模式(pattern) 与大括号框起来的操作(action) 组合而成的,或许,还会加上实现操作细节的函数(function ) .针对每个匹配于输人数据的模式 ...
- How to set spring boot active profiles with maven profiles
In the previous post you could read about separate Spring Boot builds for a local development machin ...
- Javascript中没有块级作用域(模仿)
在C/C++中,由花括号封闭的代码块都有自己的作用域,也就是块级作用域(私有作用域).而在javascript中则没有块级作用域,首先来看一段代码: function test(){ for(var ...
- hibernate MTM 联合主键
//适用于表里没有其他列,只有主键 //Course.java实体类 package com.tao.pojo; import java.util.HashSet; import java.util. ...
- bzoj5253 [2018多省省队联测]制胡窜
后缀自动机挺好毒瘤的题. 我们考虑哪些切点是不合法的.肯定是所有的匹配串都被切了. 我们考虑第一个切口的位置. 当第一个切口在第一个出现位置前时,第二个切口必须切掉所有的串. 当第一个切口在$l_{i ...
- BZOJ_3132_上帝造题的七分钟_树状数组
BZOJ_3132_上帝造题的七分钟_树状数组 Description “第一分钟,X说,要有矩阵,于是便有了一个里面写满了0的n×m矩阵. 第二分钟,L说,要能修改,于是便有了将左上角为(a,b), ...
- jquery 判断一个元素是否在数组中 $.inarry()使用
需要判断一个元素是否在一个数组里: js indexOf()方法 如果存在 则返回该元素的下标值 如果不存在则返回-1 学习源头: http://www.w3school.com.cn/jsref/ ...
- 【功耗测试环境预置自动化脚本开发】【切换wifi模式为siso模式】【用到方法*args】
import os,reimport logginglogging.basicConfig(level=logging.DEBUG, format='%(asctime)s %(filename)s[ ...