iOS NSTimer内存问题分析及解决方案

NSTimer是我们经常使用的一个跟时间相关的oc类,作用是在指定的时间触发对应的事件。

一、使用NSTimer

在iOS10.0之前有:

- (instancetype)initWithFireDate:(NSDate*)date interval:(NSTimeInterval)ti target:(id)t selector:(SEL)s userInfo:(nullableid)ui repeats:(BOOL)rep;

+ (NSTimer*)timerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullableid)userInfo repeats:(BOOL)yesOrNo;

+ (NSTimer*)scheduledTimerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullableid)userInfo repeats:(BOOL)yesOrNo;

我们试试 init构造方法:

init构造方法

结果发现日志竟然没有打印,what?

试试timer开头的构造方法:

timer构造方法

结果发现日志同样没有打印,what?

试试schedule开头的方法,会不会有奇迹出现:


schedule构造方法

奇迹出现了,what?Are you ok?好慌^_^,吓得我赶紧看了看苹果爸爸怎么说的。

Timers work in conjunction with run loops. Run loops maintain strong references to their timers.Once scheduled on a run loop, the timer fires at the specified interval until it is invalidated. 

苹果爸爸说

大致的意思是timer是工作在runloop中,并被runloop强引用,只有加入runloop才能生效呢,然后苹果爸爸又说了,init跟timer开头的没有加入到任何runloop中,schedule开头的默认加入了当前的runloop中,所以就出现了上述的情况。

好了赶快试试:

试试

果然就是这样的,苹果爸爸就是折腾人,干嘛不一样处理呢?俗话说,想灭之,必先予之,哦哦,玩笑,苹果爸爸肯定不是这样想,可能出于开发者的角度,更多的自主权。

二、NSTimer准确么?

答案是不准的,有两点需要说明:

1、iOS7.0后,timer加入runloop后,并不是准确的在指定的时间点触发事件,是有一个容忍度的,大概在10%左右,苹果爸爸的原话如下:In iOS 7 and later and macOS 10.9 and later, you can specify a tolerance for a timer (tolerance). This flexibility in when a timer fires improves the system's ability to optimize for increased power savings and responsiveness. The timer may fire at any time between its scheduled fire date and the scheduled fire date plus the tolerance. The timer doesn't fire before the scheduled fire date. For repeating timers, the next fire date is calculated from the original fire date regardless of tolerance applied at individual fire times, to avoid drift. The default value is zero, which means no additional tolerance is applied. The system reserves the right to apply a small amount of tolerance to certain timers regardless of the value of the tolerance property.

As the user of the timer, you can determine the appropriate tolerance for a timer. A general rule, set the tolerance to at least 10% of the interval, for a repeating timer. Even a small amount of tolerance has significant positive impact on the power usage of your application. The system may enforce a maximum value for the tolerance.

2、runloop分5中mode,其中三种模式是开发者可以接触的,NSRunLoopCommonModesNSDefaultRunLoopModeUITrackingRunLoopMode。timer加入某种mode,只有会在对应的mode中触发事件,如果加入NSDefaultRunLoopMode,那么当runloop切换到UITrackingRunLoopMode(例如滑动UIScrollView)下,那么timer就会停止,直到切回NSDefaultRunLoopMode中,timer才会继续fire。

所以说,如果需要精准的触发事件,那么建议通过GCD timer,因为CGD timer是通过内核port触发runloop的机制,就能够达到精准制导!

三、关于内存泄露

1、内存泄漏分析:

什么,what?这个东西还有内存泄漏,不是ARC么?Are you ok?

是的,没错,的确有内存泄漏的风险。上面苹果爸爸的文档里就说了,timer会被runloop强引用,直到被invalidate,他们关系是这样的:

关系图

根据上面的关系图,如果在target的dealloc方法里面invalidate掉timer,就会形成相互等待,造成循环引用,如下图:

内存泄漏的写法

2、内存泄漏解决方案:

这简单,不再dealloc里面invalidate,在其他的时机去做这步,这样当然没问题,给你点个赞^ _ ^,但是有些需求我们就需要在整个生命周期内都需要维持timer,只能在dealloc里面走这步,怎么办?还是从关系图分析开始:

权威认证通过的解决方案

有些同学可能有疑问,既然是timer强引用target,我给他一个弱引用给他怎么样?是的,你想得有道理,但是,即使你传一个经过__weak修饰得self给timer,依旧然并卵,timer依旧会是强引用了target,不知道苹果爸爸内部对他做了什么手脚,已经哭晕在厕所,😄,就是下图的方式依旧不行:

__weak没法打断强引用

2.1、第三方作为target:

其实构造方法里面还有一种方式没用:+ (NSTimer*)timerWithTimeInterval:(NSTimeInterval)ti invocation:(NSInvocation*)invocation repeats:(BOOL)yesOrNo;

invocation构造方法

亲测依旧没有打断强引用,但是给我们提供了一种思路,那就是第三方,oc有一个类开发基本上没用过,就是虚基类NSProxy:

NSProxy

大致意思就是用来转发消息用的。

NSProxy

子类需要重写两个方法:forwardInvocation: 和methodSignatureForSelector: 。具体用法参考NSProxy的简单使用,好了不多说,直接撸代码:

TimerProxy
TimerProxy
TestViewController

现在让我们看看类图结构:

加入第三方,打断循环引用

2.2、block打断:

不多说,直接上代码:

NSTimer分类.h
NSTimer分类.m
block验证

其实呢苹果爸爸在iOS10.0以后,也意识到这个问题,这么简单的问题非得让开发者折腾,好吧,这个问题,我来解决,所以就有了iOS10.0的新版本API,新增了对应的block版本的构造方法:

+ (NSTimer*)timerWithTimeInterval:(NSTimeInterval)interval repeats:(BOOL)repeats block:(void(^)(NSTimer*timer))blockAPI_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0));

+ (NSTimer*)scheduledTimerWithTimeInterval:(NSTimeInterval)interval repeats:(BOOL)repeats block:(void(^)(NSTimer*timer))blockAPI_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0));

- (instancetype)initWithFireDate:(NSDate*)date interval:(NSTimeInterval)interval repeats:(BOOL)repeats block:(void(^)(NSTimer*timer))blockAPI_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0));

好了,就写到这了,NSTimer就这些,欢迎指正!!!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,236评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,867评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,715评论 0 340
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,899评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,895评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,733评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,085评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,722评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,025评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,696评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,816评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,447评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,057评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,009评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,254评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,204评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,561评论 2 343

推荐阅读更多精彩内容