demo放在了GitHub

内存泄漏的原因:

self强引用timer。timer添加在runloop上,只要timer不销毁self就销毁不了。当然了你可以选择在viewWillDisappear中销毁timer。但是定时器页面不一定都是pop到上一个页面,也有可能push新页面,也有可能是进入后台,这样我们希望重新回到定时器页面的时候,定时任务还依旧是执行状态。所以invalidate放到viewWillDisappear是不合理的,唯一合理的地方就是定时器页面销毁的时候销毁timer。这就引出了以下三种解决方法。

第一、二种方法是在合适的时机销毁timer,干掉强引用。

第三种方法是自定义timer,弱引用timer,从源头上就不形成循环引用,更不会导致内存泄漏。

一、离开当前控制器销毁NSTimer

didMoveToParentViewController方法了解一下

@interface WLZSecondController ()

@property (nonatomic, strong)UILabel *label;

@property (nonatomic, assign)int repeatTime;

//第一、二种的属性

//@property (nonatomic, strong)NSTimer *timer;

//第三种方法的属性

@property (nonatomic, strong)WLZTimer *timer;

@end

@implementation WLZSecondController

- (void)didReceiveMemoryWarning {

[super didReceiveMemoryWarning];

// Dispose of any resources that can be recreated.

}

- (void)viewDidLoad {

[super viewDidLoad];

self.view.backgroundColor = [UIColor yellowColor];

self.repeatTime = 60;

self.label = [[UILabel alloc] initWithFrame:CGRectMake(100, 100, 200, 50)];

self.label.text = @"60";

self.label.textColor = [UIColor blackColor];

[self.view addSubview:self.label];

[self createTimer];

}

#pragma mark - 第一种方法

- (void)createTimer{

self.timer = [NSTimer timerWithTimeInterval:1 target:self selector:@selector(change) userInfo:nil repeats:YES];

[[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSDefaultRunLoopMode];

}

- (void)didMoveToParentViewController:(UIViewController *)parent{

if(parent == nil){

[self.timer invalidate];

}

}

- (void)change{

self.repeatTime --;

self.label.text = [NSString stringWithFormat:@"%d",self.repeatTime];

if(self.repeatTime == 0){

[self.timer invalidate];

}

}

- (void)dealloc{

NSLog(@"dealloc");

}

复制代码

二、自定义返回按钮销毁NSTimer

这种方法就需要禁止掉侧滑返回手势。

#pragma mark - 第二种方法 这里只是随意创建了一个button,具体的图片、文案可以自己调试。

- (void)createTimer{

self.timer = [NSTimer timerWithTimeInterval:1 target:self selector:@selector(change) userInfo:nil repeats:YES];

[[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSDefaultRunLoopMode];

}

- (void)viewWillAppear:(BOOL)animated{

[self changeBackBarButtonItem];

}

- (void)changeBackBarButtonItem{

self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemDone target:self action:@selector(invalidateTimer)];

}

- (void)invalidateTimer{

[self.timer invalidate];

[self.navigationController popViewControllerAnimated:YES];

}

- (void)change{

self.repeatTime --;

self.label.text = [NSString stringWithFormat:@"%d",self.repeatTime];

if(self.repeatTime == 0){

[self.timer invalidate];

}

}

- (void)dealloc{

NSLog(@"dealloc");

}

复制代码

三、自定义timer,压根不形成循环引用

自定义timer类

.h文件

@interface WLZTimer : NSObject

@property (nonatomic, weak)id target;

@property (nonatomic, assign)SEL selector;

///创建timer

- (instancetype)initWithTimeInterval:(NSTimeInterval)interval Target:(id)target andSelector:(SEL)selector;

///销毁timer

- (void)closeTimer;

@end

.m文件

@interface WLZTimer ()

@property (nonatomic, strong)NSTimer *timer;

@end

@implementation WLZTimer

- (instancetype)initWithTimeInterval:(NSTimeInterval)interval Target:(id)target andSelector:(SEL)selector{

if(self == [super init]){

self.target = target;

self.selector = selector;

self.timer = [NSTimer scheduledTimerWithTimeInterval:interval target:self selector:@selector(dosomething) userInfo:nil repeats:YES];

}

return self;

}

- (void)dosomething{

//这里是为了不阻塞主线程

dispatch_async(dispatch_get_main_queue(), ^{

id target = self.target;

SEL selector = self.selector;

if([target respondsToSelector:selector]){

[target performSelector:selector withObject:nil];

}

});

}

- (void)closeTimer{

[self.timer invalidate];

self.timer = nil;

}

- (void)dealloc{

NSLog(@"WLZTimer dealloc");

}

@end

复制代码

自定义timer在主线程异步执行任务不明白原因的话,可参考文章iOS线程、同步异步、串行并行队列

创建timer的时候就用自定义的类就可以

#pragma mark - 第三种方法

/*

* 与第前两种不同的是:前两种只是在合适的时机解决循环引用,

* 第三种根本就不会造成循环引用,可以封装起来供多个地方使用,而且遵循单一职责原则

*

*/

- (void)createTimer{

self.timer = [[WLZTimer alloc] initWithTimeInterval:1 Target:self andSelector:@selector(change)];

}

- (void)change{

self.repeatTime --;

self.label.text = [NSString stringWithFormat:@"%d",self.repeatTime];

if(self.repeatTime == 0){

[self.timer closeTimer];

}

}

- (void)dealloc{

[self.timer closeTimer];

NSLog(@"dealloc");

}

复制代码

2019/1/17 更新

四、定义中介继承NSObject进行消息转发消除强引用NSTimer

+ (instancetype)proxyWithTarget:(id)aTarget{

TFQProxy *proxy = [[TFQProxy alloc] init];

proxy.target = aTarget;

return proxy;

}

//自己不能处理这个消息,就会调用这个方法来消息转发,return target,让target来调用这个方法。

- (id)forwardingTargetForSelector:(SEL)aSelector{

if([self.target respondsToSelector:aSelector]){

return self.target;

}

return nil;

}

复制代码self.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:[TFQProxy proxyWithTarget:self] selector:@selector(repeatAction:) userInfo:nil repeats:YES];

复制代码

五、定义中介继承NSProxy进行消息转发消除强引用NSTimer

+ (instancetype)proxyWithTarget:(id)aTarget{

TFQProxySubclass *proxy = [TFQProxySubclass alloc];

proxy.target = aTarget;

return proxy;

}

//为另一个类实现的消息创建一个有效的方法签名

- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel{

return [self.target methodSignatureForSelector:sel];

}

//将选择器转发给一个真正实现了该消息的对象

- (void)forwardInvocation:(NSInvocation *)invocation{

[invocation invokeWithTarget:self.target];

}

复制代码self.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:[TFQProxySubclass proxyWithTarget:self] selector:@selector(repeatAction:) userInfo:nil repeats:YES];

复制代码

tips:五比四效率高,因为5是系统的类,直接进行消息转发,4会走几条弯路才会到消息转发那个方法

六、GCD创建定时器

/**

GCD创建定时器

@param task 定时器内容

@param interval 执行间隔

@param repeat 是否重复

@param async 是否异步

@param identifier 定时器唯一ID

@return 返回定时器唯一ID,销毁的时候用

*/

+ (NSString *)schedleTask:(void (^)(void))task interval:(NSTimeInterval)interval repeat:(BOOL)repeat async:(BOOL)async reuseIdentifier:(NSString *)identifier{

dispatch_queue_t queue = async ? dispatch_get_global_queue(0, 0) : dispatch_get_main_queue();

//穿件定时器

dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);

//开始时间

dispatch_time_t start = dispatch_time(DISPATCH_TIME_NOW, 0*NSEC_PER_SEC);

//设置各种时间

dispatch_source_set_timer(timer, start, interval*NSEC_PER_SEC, 0);

//设置回调

dispatch_source_set_event_handler(timer, ^{

task();

if(!repeat){

[self cancelTimer:identifier];

}

});

//启动定时器

dispatch_resume(timer);

timerDict[identifier] = timer;

return identifier;

}

+ (void)cancelTimer:(NSString *)identifier{

dispatch_source_cancel(timerDict[identifier]);

[timerDict removeObjectForKey:identifier];

}

复制代码__weak typeof(self) weakSelf = self;

self.timerIdentifier = [TFQGCDTimer schedleTask:^{

[weakSelf repeatAction:nil];

} interval:1 repeat:YES async:NO reuseIdentifier:@"identifier"];

复制代码

nstimer循环引用_解决NSTimer循环引用导致内存泄漏的六种方法相关推荐

  1. 解决Solaris应用程序开发内存泄漏问题

    作者: 李凌云,张一峰(laoeyu) 内存泄漏是应用软件开发过程中经常会遇到的问题,应用长期内存泄漏会占用大量操作系统内存资源,直接导致应用程序运行不稳定,严重时甚至还会影响到操作系统的正常运行.为 ...

  2. python会不会内存泄露_总结python 三种常见的内存泄漏场景

    概要 不要以为 Python 有自动垃圾回收就不会内存泄漏,本着它有"垃圾回收"我有"垃圾代码"的精神,现在总结一下三种常见的内存泄漏场景. 无穷大导致内存泄漏 ...

  3. 利用classloader同一个项目中加载另一个同名的类_线程上下文类加载器ContextClassLoader内存泄漏隐患...

    前提 今天(2020-01-18)在编写Netty相关代码的时候,从Netty源码中的ThreadDeathWatcher和GlobalEventExecutor追溯到两个和线程上下文类加载器Cont ...

  4. 会不会导致内存泄漏_可能会导致.NET内存泄露的8种行为

    原文连接:https://michaelscodingspot.com/ways-to-cause-memory-leaks-in-dotnet/ 作者 Michael Shpilt.授权翻译,转载请 ...

  5. python elif报错_解决python循环的elif报错的方法

    解决python循环的elif报错的方法 发布时间:2020-08-05 15:36:51 来源:亿速云 阅读:96 作者:小新 解决python循环的elif报错的方法?这个问题可能是我们日常学习或 ...

  6. sqlserver2008未将对象引用设置到对象的实例_面试官:ThreadLocal 的内存泄漏是弱引用导致的,你确定?...

    面试官:ThreadLocal 了解吗? Python 小星:线程局部变量,多线程下能保证各个线程的变量相对独立于其他线程的变量. 面试官:那你说下它是如何保证线程隔离的? Python 小星:每个线 ...

  7. python字典弱引用_如何使用弱引用优化 Python 程序的内存占用?

    Python 的垃圾回收机制通过引用计数来决定一个对象要不要被回收.当一个对象被引用次数为0时,它就会被作为垃圾回收从而释放 Python 内存.但有些情况下,我们的代码可能在不经意间导致某些实际上我 ...

  8. python有几种循环语句_[14] Python循环语句(一)

    1. 概述 今天我们介绍循环语句,和条件判断一样,我们从流程图开始看起.首先看一下学习计划列表,粗体为已学,斜体为新增或修改内容.计算机编程的原理简要介绍 集成开发环境PyCharm 变量名.数字.字 ...

  9. for循环递减_讲讲关于循环的那些事

    每个人一生中都至少应该获得一次全场起立鼓掌的机会,因为我们都曾胜过这个世界.-R.J.帕拉西奥<奇迹男孩> 导言:希腊哲学家Zeno曾经说"运动是不可能的.由于运动的物体在到达目 ...

最新文章

  1. 任正非签发最新电邮:过去我们是为了赚点小钱,现在是要战胜美国
  2. linux系统知识 - 信号基础
  3. Python中print()使用格式示例收集
  4. 面试必会系列 - 1.6 Java 垃圾回收机制
  5. android 编译共享ccache的缓存
  6. Spring Boot 是什么,有什么用。
  7. 【今日CS 视觉论文速览】 25 Jan 2019
  8. spring bean
  9. JavaEE Tutorials (9) - 运行持久化示例
  10. python中间件有哪些_python_21(Django中间件)
  11. 用CST进行多物理仿真,热仿真结果有误
  12. ArcEngine旋转IRotateTracker
  13. 诸葛 理解产品、交互和运营
  14. Mac设置顶部菜单栏技巧?
  15. l003 Driller Augmenting Fuzzing Through Selective Symbolic Execution_2016_NDSS学习笔记
  16. pta——出生年,查验身份证(c语言)
  17. Remove Duplicates
  18. 怎么安装打印机驱动?有没有快捷的方法?
  19. 藏拙的搜索引擎技术是2008年全宇宙最耀眼的搜索引擎技术吗?
  20. 基于PHP的快递查询免费开放平台案例-快宝开放平台

热门文章

  1. 识别图片并可视化_识别交通锥,特斯拉驾驶信任提升的一小步
  2. 打开浏览器提示下载解决方法
  3. 使用chrome下载m3u8视频
  4. Web系统中Mic设备的应用实例
  5. Reporting service个人使用经验
  6. .NET Core 配置Configuration杂谈
  7. 简单记事本及目录树形图的Java实现
  8. springcloud(三):服务提供与调用
  9. Mysql报错Fatal error: Can#39;t open and lock privilege tables: Table #39;mysql.host#39; doesn#39;t...
  10. #大学生活#锐捷客户端与VMWare