前言

在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. popup_layer插件示例

    导入popup_layer.js插件 设置好显示的div: <div class="main" id="showImg" style="disp ...

  2. Mac命令行

    参考:http://www.cnblogs.com/-ios/p/4949923.html 必读 涵盖范围: 这篇文章对刚接触命令行的新手以及具有命令行使用经验的人都有用处.本文致力于做到覆盖面广(尽 ...

  3. Java生成名片式的二维码源码分享

    世界上25%的人都有拖延症——但我觉得这统计肯定少了,至少我就是一名拖延症患者.一直想把“Java生成名片式(带有背景图片.用户网络头像.用户昵称)的二维码”这篇博客分享出来,但一直拖啊拖,拖到现在, ...

  4. open_basedir restriction in effect,解决php引入文件权限问题

    一.前言 今天在Ubuntu安装了lnmp环境,运行项目的时候出现了,引入500的错误 二.查看错误 再项目文件入口添加,代码显示错误内容,查看到一下错误 ini_set('display_error ...

  5. 关于多条数据转为json格式单次传输的问题 2017.05.27

    数据形式如下: var mycars = [];//定义数组存放多条数据 for(var i=0;i<2;i++){ var jsonData = {};//定义变量存放单条数据 jsonDat ...

  6. Javascript 中的map/reduce

    先填个坑,后面慢慢填 附上一篇不错的文章:https://segmentfault.com/a/1190000008719824

  7. 跟我学ASP.NET MVC之三:完整的ASP.NET MVC程序-PartyInvites

    摘要: 在这篇文章中,我将在一个例子中实际地展示MVC. 场景 假设一个朋友决定举办一个新年晚会,她邀请我创建一个用来邀请朋友参加晚会的WEB程序.她提出了四个注意的需求: 一个首页展示这个晚会 一个 ...

  8. python基础下的mysql学习之操作命令(一)

    E-R模型简介: 该模型应用于数据库设计中的概念结构设计阶段 E-entry,实体,是指设计的主体.对象等: R-relationship,关系,是指设计的主体之间的联系,关系也是一种数据: 一个实体 ...

  9. QM3_Statistics Concepts and Market Returns

    Basic Concepts Terms Descriptive Statistics Describes the important aspects of large data sets. 统计 概 ...

  10. Java 读书笔记 (七) 变量

    Java语言中, 所有的变量在使用前必须声明.声明变量的基本格式: type identifier [ =value],[,identifier [=value]...]; 声明变量的实例,有些包含了 ...