前言

在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循环引用的几种解决方案的更多相关文章

  1. CADisplayLink、NSTimer循环引用解决方案

    前言:CADisplayLink.NSTimer 循环引用问题 ​ CADisplayLink.NSTimer会对Target产生强引用,如果target又对他们产生强引用,那么就会引发循环引用. @ ...

  2. iOS容易造成循环引用的三种场景

    iOS容易造成循环引用的三种场景  ARC已经出来很久了,自动释放内存的确很方便,但是并非绝对安全绝对不会产生内存泄露.导致iOS对象无法按预期释放的一个无形杀手是--循环引用.循环引用可以简单理解为 ...

  3. 解决NSTimer循环引用Retain Cycle问题

    解决NSTimer循环引用Retain Cycle问题 iOS开发中以下的情况会产生循环引用 block delegate NSTimer 循环引用导致一些对象无法销毁,一定的情况下会对我们横须造成影 ...

  4. 解决NSTimer循环引用

    NSTimer常见用法 @interface XXClass : NSObject - (void)start; - (void)stop; @end @implementation XXClass ...

  5. 【转】iOS学习之容易造成循环引用的三种场景

    ARC已经出来很久了,自动释放内存的确很方便,但是并非绝对安全绝对不会产生内存泄露.导致iOS对象无法按预期释放的一个无形杀手是——循环引用.循环引用可以简单理解为A引用了B,而B又引用了A,双方都同 ...

  6. 【原】iOS容易造成循环引用的三种场景,就在你我身边!

    ARC已经出来很久了,自动释放内存的确很方便,但是并非绝对安全绝对不会产生内存泄露.导致iOS对象无法按预期释放的一个无形杀手是——循环引用.循环引用可以简单理解为A引用了B,而B又引用了A,双方都同 ...

  7. iOS-容易造成循环引用的三种场景

    ARC已经出来很久了,自动释放内存的确很方便,但是并非绝对安全绝对不会产生内存泄露.导致iOS对象无法按预期释放的一个无形杀手是——循环引 用.循环引用可以简单理解为A引用了B,而B又引用了A,双方都 ...

  8. iOS 容易引“起循环引用”的三种场景

    笔者在阅读中总结了一下,在iOS平台容易引起循环引用的四个场景: 一.parent-child相互持有.委托模式 [案例]:   @interface FTAppCenterMainViewContr ...

  9. 用block解决nstimer循环引用

    大多数开发者可能都会这样来实现定时器.创建定时器的时候,由于目标对象是self,所以要保留此实例.然而,因为定时器是用实例变量存放的,所以实例也保留了定时器,这就造成了循环引用.除非调用stop方法, ...

随机推荐

  1. 通俗的讲法理解spring的事务实现原理

    拿房屋买卖举例,流程:销售房屋 -- 接待员 -- 销售员 -- 财务 售楼处 存放着所有待售和已售的房屋数据(数据源 datasource) 总经理 带领一套自己的班底,下属员工都听自己的,服务于售 ...

  2. dubbo-admin和dubbo-monitor的安装

    一.安装dubbo-admin 去这里 http://download.csdn.net/download/u013081610/10044744 下载dubbo-admin.war 部署dubbo- ...

  3. 20.如何从app业务逻辑提炼api接口

    在app后端的工作中,设计api是一个很考验设计能力的工作.在项目的初始阶段,只知道具体的业务逻辑,那怎么把业务逻辑抽象和提炼,设计出api呢?通过阅读本文,可解答以上疑惑. 在本文中,是用以前做过的 ...

  4. ATM机

    ATM 要求 示例代码: https://github.com/triaquae/py_training/tree/master/sample_code/day5-atm

  5. 测试网页时需要添加等待的情形 (Selenium)

    测试网页时需要添加等待的情形: 1. 网页跳转 2. DOM结构发生改变.

  6. git本地克隆时失败: SSL certificate problem

    问题描述 将git包在本地克隆时出现这个错误. 解决办法 找到.gitconfig文件,在http项添加 sslVerify = false. 注: 上面这个是针对单一库的,如果希望对所有库都关闭ss ...

  7. classpath和filepath

    ******************************** java中的相对路径和绝对路径 ******************************** 相对路径(其实就是编译后的路径) - ...

  8. BZOJ1854: [Scoi2010]游戏 二分图

    很早之前写的题了,发现没有更博,想了想,更一发出来. Orz ljss 这是冬令营上的例题...之后,我推出来了一种时间复杂度没有问题,空间复杂度没有问题的方法,额(⊙o⊙)…和给出的正解不同,但是能 ...

  9. mybatis 异常Result Maps collection does not contain value for java.lang.String

    Result Maps collection does not contain value for java.lang.String 以上是我报的错. 只要报Result Maps collectio ...

  10. Java解析表达式

    需求 思路 总结 需求 指定一个String表达式,表达式符合给出的运算符规范,比如:2!=2 and 2>=3 or 4<=4,计算出表达式的结果(true or false). 支持的 ...