偶得前言

本篇文章中我们主要谈谈NSTimer\CADisplayLink在使用过程中牵扯到内存泄露的相关问题及解决思路(文章末尾会附上Demo),有时候我们在不知情的情况容易入坑,最关键你还不知道自己掉坑了,闲话不多说,让我们开始进入正题。

NSRunLoop与定时器

我们先来回顾一下NSRunLoopNSTimer\CADisplayLink的影响。(为了方便,以下统称定时器)

大家都知道定时器的运行需要结合一个NSRunLoop(有疑惑的同学可以查看Xcode Document,此处不细说),同时NSRunLoop对该定时器会有一个强引用,这也是为什么我们不对NSRunLoop中的定时器进行强引的原因(如:self.timer = timer, 此代码可省略)。

- invalidate的作用

由于NSRunLoop对定时器有着牵引,那么问题就来了,那么定时器怎样才能被释放掉呢(先不考虑使用removeFromRunLoop:),此时- invalidate函数的作用就来了,我们来看看官方就此函数的介绍:

Removes the object from all runloop modes (releasing the receiver if it has been implicitly retained) and releases the 'target' object.

据官方介绍可知,- invalidate做了两件事,首先是把本身(定时器)从NSRunLoop中移除,然后就是释放对‘target’对象的强引用。从而解决定时器带来的内存泄露问题。

内存泄露在哪?

看到这里我们可能会有点懵逼,先上一个图(为了方便讲解,途中箭头指向谁就代表强引谁):

此处我们必须明确,在开发中,如果创建定时器只是简单的计时,不做其他引用,那么timer对象与myClock对象循环引用的问题就可以避免(即省略self.timer = timer,前文已经提到过,不再阐述),即图中箭头5可避免。

虽然孤岛问题已经避免了,但还是存在问题,因为myClock对象被UIViewController以及timer引用(timer直接被NSRunLoop强引用着),当UIViewController控制器被UIWindow释放后,myClock不会被销毁,从而导致内存泄露。

讲到这里,有些人可能会说对timer对象发送一个invalidate消息,这样NSRunLoop即不会对timer进行强引,同时timer也会释放对myClock对象的强引,这样不就解决了吗?没错,内存泄露是解决了。

但是,这并不是我们想要的结果,在开发中我们可能会遇到某些需求,只有在myClock对象要被释放时才去释放timer(此处要注意释放的先后顺序及释放条件),如果提前向timer发送了invalidate消息,那么myClock对象可能会因为timer被提前释放而导致数据错了,就像闹钟失去了秒针一样,就无法正常工作了。所以我们要做的是在向myClock对象发送dealloc消息前在给timer发送invalidate消息,从而避免本末倒置的问题。这种情况就像一个死循环(因为如果不给timer发送invalidate消息,myClock对象根本不会被销毁,dealloc方法根本不会执行),那么该怎么做呢?

我们如何解决?

现在我们已经知道内存泄露在哪了,也知道原因是什么,那么如何解决,或者说怎样优雅的解决这问题呢?方式有很多.

a.NSTimer Target

为了解决timer与myClock之间类似死锁的问题,我们会将定时器中的‘target’对象替换成定时器自己,采用分类实现。

#import "NSTimer+TXTimerTarget.h"

@implementation NSTimer (TXTimerTarget)

+ (NSTimer *)tx_scheduledTimerWithTimeInterval:(NSTimeInterval)interval repeat:(BOOL)yesOrNo block:(void (^)(NSTimer *))block{
return [self scheduledTimerWithTimeInterval:interval target:self selector:@selector(startTimer:) userInfo:[block copy] repeats:yesOrNo];
} + (void)startTimer:(NSTimer *)timer {
void (^block)(NSTimer *timer) = timer.userInfo;
if (block) {
block(timer);
}
}
@end

b.NSTimer Proxy

这种方式就是创建一个NSProxy子类TXTimerProxy(不太清楚NSProxy的同学可以去查一下相关资料哈),TXTimerProxy的作用是什么呢?就是什么也不做,可以说只会重载消息转发机制,如果创建一个TXTimerProxy对象将其作为timer的‘target’,专门用于转发timer消息至myClock对象,那么问题是不是就解决了呢?答案:是的。

NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:0.25 target:[TXTimerProxy timerProxyWithTarget:self] selector:@selector(startTimer) userInfo:nil repeats:YES];

[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];

self.timer = timer;

实现详情文章末尾会附上Demo,感兴趣的同学可以去看看哈,有什么问题可以直接问,互相交流。

c.NSTimer Block

还有一种方式就是采用Block,iOS 10增加的API。

+ scheduledTimerWithTimeInterval:repeats:block:

The execution body of the timer; the timer itself is passed as the parameter to this block when executed to aid in avoiding cyclical references

有点类似a方式,此处不再详述。

//NSTimer Block(解决self内存泄露) 模拟器会崩溃
//API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0));
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:0.25 repeats:YES block:^(NSTimer * _Nonnull timer) {
NSLog(@"TXNSTimerBlockController timer start");
}]; [[NSRunLoop mainRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
self.timer = timer;

此处以NSTimer举例,CADisplayLink不再详述(方式都是一样)。

以上纯属个人看法和观点,如有不妥或不对之处,请指出,互相交流,欢迎一起讨论,谢谢_

TXTimerLeaksDemo链接:https://github.com/tingxins/TXTimerLeaksDemo

浅析NSTimer & CADisplayLink内存泄露的更多相关文章

  1. 浅析造成 JS 内存泄露的几种原因及解决方案

    内存泄露是指一块被分配的内存既不能使用,又不能回收,直到浏览器进程结束.在C++中,因为是手动管理内存,内存泄露是经常出现的事情.而现在流行的C#和Java等语言采用了自动垃圾回收方法管理内存,正常使 ...

  2. NSTimer注意内存泄露(真该死)

    NSTimer可以用来执行一些定时任务,比较常用的方法就是: + (NSTimer *)timerWithTimeInterval:(NSTimeInterval)ti target:(id)aTar ...

  3. 项目问题总结:Block内存泄露 以及NSTimer使用问题

    BLock的内存泄露 在我们代码中关于block的使用可以说随处可见,第一次接触block的时候是关于UIView的块动画,那时觉得block的使用好神奇,再后来分析总结为block其实就是一个c语言 ...

  4. NSTimer、CADisplayLink 内存泄漏

    NSTimer.CADisplayLink 内存泄漏 内存泄漏的原因 CADisplayLink 要用 Taget 和 Selector 初始化,NSTimer 也可以用类似的方法初始化.这样初始化之 ...

  5. Chrome V8系列--浅析Chrome V8引擎中的垃圾回收机制和内存泄露优化策略

    V8 实现了准确式 GC,GC 算法采用了分代式垃圾回收机制.因此,V8 将内存(堆)分为新生代和老生代两部分. 一.前言 V8的垃圾回收机制:JavaScript使用垃圾回收机制来自动管理内存.垃圾 ...

  6. JavaScript垃圾回收(三)——内存泄露

    一.JavaScript内存监测工具 在讨论内存泄露之前,先介绍几款JavaScript内存监测工具. IE的sIEve与JSLeaksDetector(这两个可以在下面的附件中下载),firefox ...

  7. 在iOS上自动检测内存泄露

    手机设备的内存是一个共享资源.应用程序可能会不当的耗尽内存.崩溃,或者遭遇大幅度的性能降低. Facebook iOS客户端有很多功能,并且它们共享同一块内存空间.如果任何特定的功能消耗过多的内存,就 ...

  8. ARC下的内存泄露

    iOS提供了ARC功能,很大程度上简化了内存管理的代码. 但使用ARC并不代表了不会发生内存泄露,使用不当照样会发生内存泄露. 下面列举两种ARC导致内存泄露的情况. 1,循环参照 A有个属性参照B, ...

  9. java: web应用中不经意的内存泄露

    前面有一篇讲解如何在spring mvc web应用中一启动就执行某些逻辑,今天无意发现如果使用不当,很容易引起内存泄露,测试代码如下: 1.定义一个类App package com.cnblogs. ...

随机推荐

  1. codevs 1733 聪明的打字员 (Bfs)

    /* Bfs+Hash 跑的有点慢 但是codevs上时间限制10s 也ok */ #include<iostream> #include<cstdio> #include&l ...

  2. PLSQL编程基础

    一 PL/SQL简介 1 SQL:结构化的查询语句 2 PL/SQL优点与特性: 提高运行效率==>>提高运行效率的其他方式(存储过程,分页,缓存,索引) 模块化设计 允许定义标识符(变量 ...

  3. 服务器端调用Word组件读取Word权限、未将对象引用到对象实例终极解决方案

    最近因为业务需要,需要在服务器上调用Word组件,结果遇到各种问题,比如检索 COM 类工厂中 CLSID 为 {000209FF-0000-0000-C000-000000000046} 的组件失败 ...

  4. Android开发手记(9) DatePickerDialog 和 TimePickerDialog

    1.DatePickerDialog  用于获取用户输入的日期信息.其原型为: public DatePickerDialog(Contex contex, DatePickerDialog.OnDa ...

  5. colorful-记录好看的颜色

    p { float: left; width: 100px; height: 100px; border: 1px solid black; margin: 5px; text-align: cent ...

  6. hadoop2.4.1伪分布式搭建

    1.准备Linux环境 1.0点击VMware快捷方式,右键打开文件所在位置 -> 双击vmnetcfg.exe -> VMnet1 host-only ->修改subnet ip ...

  7. jquery mini ui 学习

    1.mini.parse(); 将html标签解析为miniui控件.解析后,才能使用mini.get获取到控件对象. 2.mini.get(id);根据id获取控件对象. 3.grid.load() ...

  8. PHP Cookies

    PHP Cookies cookie 常用于识别用户. Cookie 是什么? cookie 常用于识别用户.cookie 是一种服务器留在用户计算机上的小文件.每当同一台计算机通过浏览器请求页面时, ...

  9. GUI对话框

    消息对话框 public static void showMessageDialog(Component parentComponent,String message,String title,int ...

  10. html表格table设置边框

    对于很多初学HTML的人来说,表格<table>是最常用的标签了,但对于表格边框的控制,很多初学者却不甚其解. 一般我们用表格的时候总会给它个border属性,比如:<table b ...