NSTimer、CADisplayLink 内存泄漏

内存泄漏的原因

CADisplayLink 要用 Taget 和 Selector 初始化,NSTimer 也可以用类似的方法初始化。这样初始化之后,NSTimer 或 CADisplayLink(以下把两者统称为 CADisplayLink)会强引用 Target。当 CADisplayLink 加入 NSRunLoop 中,NSRunLoop 会强引用 CADisplayLink。直到 CADisplayLink 调用 invalidate 方法,CADisplayLink 才会被 NSRunLoop 移除,CADisplayLink 也不再强引用 Target。

通常在 UIViewController 或 UIView 中创建 CADisplayLink,Target 是 UIViewController 或 UIView。如果只在 UIViewController 或 UIView 的 dealloc 方法中调用 CADisplayLink 的 invalidate 方法,会造成内存泄漏。因为 NSRunLoop 一直都在,CADisplayLink 不释放,Target 被强引用,Target 的 dealloc 方法不会被调用,CADisplayLink 的 invalidate 方法也不被调用,CADisplayLink 不会从 NSRunLoop 中移除。参见示意图,实线箭头表示强引用

NSRunLoop —> CADisplayLink —> Target

另外,为了在 Target 的 dealloc 方法中调用 CADisplayLink 的 invalidate 方法,CADisplayLink 可能作为 Target 的属性被强引用,这就形成 CADisplayLink 和 Target 的互相强引用,造成内存泄漏。

解决方法

改变 invalidate 方法的调用时机

例如,在 UIViewController 的 viewDidDisappear: 方法中,或者在 CADisplayLink 实现的动画结束后,或者在其他合适的时机调用 CADisplayLink 的 invalidate 方法。这样就把 CADisplayLink 从 NSRunLoop 中移除,CADisplayLink 也不再强引用 Target。即使 Target 强引用 CADisplayLink,在 Target 被释放后,CADisplayLink 也会被释放。

通过 NSProxy 避免 CADisplayLink 对 Target 的强引用

如果找不到合适的时机调用 CADisplayLink 的 invalidate 方法,那么还是在 dealloc 方法中调用 invalidate 方法,同时用 NSProxy 避免 CADisplayLink 对 Target 的强引用。开源库 FLAnimatedImage 中的 FLAnimatedImageView 用 FLWeakProxy,避免 CADisplayLink 的强引用。公开的方法只有初始化方法

@interface FLWeakProxy : NSProxy

+ (instancetype)weakProxyForObject:(id)targetObject;

@end

使用时,把 self 套一层 FLWeakProxy 即可防止 self 被 CADisplayLink 强引用。参见示意图,虚线箭头表示弱引用

NSRunLoop —> CADisplayLink —> Proxy - - > self

FLWeakProxy *weakProxy = [FLWeakProxy weakProxyForObject:self];
self.displayLink = [CADisplayLink displayLinkWithTarget:weakProxy selector:@selector(displayDidRefresh:)];

这样,self 的 dealloc 方法会被调用,在里面会调用 CADisplayLink 的 invalidate 方法,CADisplayLink 会被释放

- (void)dealloc
{
// Removes the display link from all run loop modes.
[_displayLink invalidate];
}

FLWeakProxy 对初始化时传入的 targetObject 进行弱引用,弱引用属性是 target。通过 Runtime 的消息转发机制 (参见 http://tech.glowing.com/cn/objective-c-runtime/) 把消息转发给 target,使 target 调用相应的方法。当 target 为空又收到消息时,把相应的方法返回值设置为空。具体代码如下

@interface FLWeakProxy ()

@property (nonatomic, weak) id target;

@end

@implementation FLWeakProxy

#pragma mark Life Cycle

// This is the designated creation method of an `FLWeakProxy` and
// as a subclass of `NSProxy` it doesn't respond to or need `-init`.
+ (instancetype)weakProxyForObject:(id)targetObject
{
FLWeakProxy *weakProxy = [FLWeakProxy alloc];
weakProxy.target = targetObject;
return weakProxy;
} #pragma mark Forwarding Messages - (id)forwardingTargetForSelector:(SEL)selector
{
// Keep it lightweight: access the ivar directly
return _target;
} #pragma mark - NSWeakProxy Method Overrides
#pragma mark Handling Unimplemented Methods - (void)forwardInvocation:(NSInvocation *)invocation
{
// Fallback for when target is nil. Don't do anything, just return 0/NULL/nil.
// The method signature we've received to get here is just a dummy to keep `doesNotRecognizeSelector:` from firing.
// We can't really handle struct return types here because we don't know the length.
void *nullPointer = NULL;
[invocation setReturnValue:&nullPointer];
} - (NSMethodSignature *)methodSignatureForSelector:(SEL)selector
{
// We only get here if `forwardingTargetForSelector:` returns nil.
// In that case, our weak target has been reclaimed. Return a dummy method signature to keep `doesNotRecognizeSelector:` from firing.
// We'll emulate the Obj-c messaging nil behavior by setting the return value to nil in `forwardInvocation:`, but we'll assume that the return value is `sizeof(void *)`.
// Other libraries handle this situation by making use of a global method signature cache, but that seems heavier than necessary and has issues as well.
// See https://www.mikeash.com/pyblog/friday-qa-2010-02-26-futures.html and https://github.com/steipete/PSTDelegateProxy/issues/1 for examples of using a method signature cache.
return [NSObject instanceMethodSignatureForSelector:@selector(init)];
} @end

转载请注明出处:http://www.cnblogs.com/silence-cnblogs/p/7583289.html

NSTimer、CADisplayLink 内存泄漏的更多相关文章

  1. 浅析NSTimer & CADisplayLink内存泄露

    偶得前言 NSRunLoop与定时器 - invalidate的作用 我们如何解决? 偶得前言 本篇文章中我们主要谈谈NSTimer\CADisplayLink在使用过程中牵扯到内存泄露的相关问题及解 ...

  2. 内存泄漏(I)

    Block 解决内存泄漏 使用 weakSelf 进行解决 NSTimer 的内存泄漏与解决方案 内存泄漏

  3. 解决NSTimer存在的内存泄漏的问题

    创建定时器会在一定的间隔后执行某些操作,一般大家会这样创建定时器,这样创建的定时,self对定时器有个引用,定时器对self也有个引用,造成了循环引用,最终造成了内存泄漏,如果定时器在做下载的操作就会 ...

  4. NSTimer内存泄漏导致控制器不调用dealloc

    创建定时器会在一定的间隔后执行某些操作,一般大家会这样创建定时器,这样创建的定时,self对定时器有个引用,定时器对self也有个引用,造成了循环引用,最终造成了内存泄漏,如果定时器在做下载的操作就会 ...

  5. 利用RunTime解决由NSTimer导致的内存泄漏

    NSTimer使用场景 用NSTimer来实现每隔一定时间执行制定的任务,例如最常见的广告轮播图,使用NSTimer实现这个功能很简单代码如下 NSTimer *_timer; _timer = [N ...

  6. NSTimer内存泄漏

    用NSTimer调用 timer = [NSTimer scheduledTimerWithTimeInterval:timerInterval target:self selector:@selec ...

  7. NSTimer使用不当引发的内存泄漏问题

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

  8. 利用NSProxy解决NSTimer内存泄漏问题

    之前写过一篇利用RunTime解决由NSTimer导致的内存泄漏的文章,最近和同事讨论觉得这样写有点复杂,然后发现有NSProxy这么好用的根类,根类,根类,没错NSProxy与NSObject一样是 ...

  9. iOS内存泄漏自动检测工具PLeakSniffer

    新款objective-C内存泄漏自动检测工具 PLeakSniffer , GitHub地址 (https://github.com/music4kid/PLeakSniffer). 背景 前些天读 ...

随机推荐

  1. new 、 delete 、 malloc 、 free 关系

    1.new . delete . malloc . free 关系 delete 会调用对象的析构函数 , 和 new 对应, free 只会释放内存, new 调用构造函数. malloc 与 fr ...

  2. myeclipse db browser 新建数据源

    Myeclipse 新建数据源 一.打开myeclipse(打开了当我没说) 二.在window选项中找到show view ,点击other,输入db,选择DB Browser 三.在DB Brow ...

  3. Django配置mysql

    我的环境为windows10+pyhton3.6+Django1.11.4 由于mysqldb不支持python3,所以django连接MySQL就不能再使用mysqldb了.故而选择了mysqlcl ...

  4. React 实现一个漂亮的 Table

    概述 对于企业级后台产品来说,Table 应该是使用最频繁的组件了,它通常比 Form 和 Chart 的使用还频繁.对于这么一个常用的组件,我们决定要把它从 RSuite 中单独出来开发,并且要具有 ...

  5. 用JS制作一个信息管理平台

    首先,介绍一些需要用到的基本知识. [JSON] JSON是数据交互中,最常用的一种数据格式. 由于各种语言的语法都不相同,在传递数据时,可以将自己语言中的数组.对象等转换为JSON字符串. 传递之后 ...

  6. linux环境下安装nginx步骤

    开始前,请确认gcc g++开发类库是否装好,默认已经安装. ububtu平台编译环境可以使用以下指令 apt-get install build-essential apt-get install ...

  7. Java企业微信开发_09_身份验证之移动端网页授权(有完整项目源码)

    注: 源码已上传github: https://github.com/shirayner/WeiXin_QiYe_Demo 一.本节要点 1.1 授权回调域(可信域名) 在开始使用网页授权之前,需要先 ...

  8. C# 委托、匿名方法、lambda简介

    在.NET中,委托,匿名方法和Lambda表达式很容易发生混淆.我想下面的代码能证实这点.下面哪一个First会被编译?哪一个会返回我们需要的结果?即Customer.ID=5.答案是6个First不 ...

  9. 201521123083《Java程序设计》第11周学习总结

    1. 本周学习总结 1.1 以你喜欢的方式(思维导图或其他)归纳总结多线程相关内容. 上周这张图没理解完,继续 2. 书面作业 本次PTA作业题集多线程 1互斥访问与同步访问完成题集4-4(互斥访问) ...

  10. 第一次作业-----四则运算题目生成(基于java)

    1.题目要求 1.除了整数以外,还要支持真分数的四则运算,真分数的运算,例如:1/6 + 1/8 = 7/24. 2.运算符为 +, −, ×, ÷. 3.并且要求能处理用户的输入,并判断对错,打分统 ...