前言

在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. new/delete和malloc/free的比较

    从C++角度上说,使用new分配堆空间可以调用类的构造函数,而malloc()函数仅仅是一个函数调用,它不会调用构造函数,它所接受的参数是一个unsigned long类型.同样,delete在释放堆 ...

  2. spring中jedis对redis的事务使用注意总结

    spring的@Transactional不支持redis的事务,并且redis的事务和其它关系型数据库的事务概念不是太一样,redis事务不支持回滚,并且一条命令出错后,后面的命令还会执行. 所以不 ...

  3. 服务器配置tomcat部署项目

    部署项目首先你需要把你的java web项目打包成war文件 在需要打包的项目上右键>选择[Export] 选中[Web]下面的[WAR file],点击[Next] 通过[Browse]选择保 ...

  4. SSM-SpringMVC-12:SpringMVC中BeanNameViewResolver这种视图解析器

    ------------吾亦无他,唯手熟尔,谦卑若愚,好学若饥------------- 视图解析器,这个很熟悉啊,之间就用过,就是可以简写/和.jsp的InternalResourceViewRes ...

  5. Linux内存使用情况以及内存泄露分析之工具与方法

    <Linux C/C++ Memory Leak Detection Tool> 1. 内存使用情况分析 1.1 系统总内存分析 通过cat /proc/meminfo,可用的物理内存=M ...

  6. 使用Maven+Nexus+Jenkins+Svn+Tomcat+Sonar搭建持续集成环境

    前言 但凡一个略有规模的项目都需要一个持续集成环境的支撑,为什么需要持续集成环境,我们来看一个例子.假如一个项目,由A.B两位程序员来协作开发,A负责前端模块,B负责后端模块,前端依赖后端.A和B都习 ...

  7. SpringCloud Eureka 报错 无法启动问题

    1.SpringCloud Eureka 报错 无法启动基本上都是spring boot的版本与spring cloud的版本不匹配导致的. <dependencyManagement> ...

  8. [SQL Server]用 C# 在 LinqPad 建立 Linked Server 跨服务器数据库操作

    在涉及老项目数据迁移的时候,数据库结构已经完全发生变化,而且需要对老数据进行特殊字段的处理,而且数据量较大,使用Navicat导出单表之后,一个表数据大概在100多万的样子,直接导出SQL执行根本行不 ...

  9. Go-技篇第一 技巧杂烩

    Go-技篇第一 技巧杂烩 一句话技巧 把你面向对象的大脑扔到家里吧,去拥抱接口.@mikegehard 学习如何使用Go的方式做事,不要把别的的编程风格强行用在Go里面.@DrNic 多用接口总比少用 ...

  10. forwardport--源码笔记--注释

    failed:", err.Error())             }         }()     }     //     log.Println("forwardPort ...