关于NSTimer的循环引用问题

关于NSTimer的介绍

1、最近在总结知识、好久没记录了,今天抽空记录下NSTimer的使用,首先我们先看下App官方API文档中解释的NSTimer

### Summary
A timer that fires after a certain time interval has elapsed, sending a specified message to a target object.
### Discussion
第一段:
Timers work in conjunction with run loops. Run loops maintain strong references to their timers, so you don’t have to maintain your own strong reference to a timer after you have added it to a run loop. 

首先看这段的解释,NSTimer的工作依赖NSRunLoop,并且RunLoop保持强持有当前的Timer, 这个时候当你把Timer添加到当前的RunLoop后,你当前的Timer就被Run Loop强持有了

To use a timer effectively, you should be aware of how run loops operate. See [Threading Programming Guide](https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Multithreading/Introduction/Introduction.html#//apple_ref/doc/uid/10000057i) for more information.

A timer is not a real-time mechanism. If a timer’s firing time occurs during a long run loop callout or while the run loop is in a mode that isn't monitoring the timer, the timer doesn't fire until the next time the run loop checks the timer. Therefore, the actual time at which a timer fires can be significantly later. See also [Timer Tolerance](applewebdata://43EE1961-4719-4FAB-9500-13E376222651#1667624).

[NSTimer](apple-reference-documentation://hcaODPrPmP) is toll-free bridged with its Core Foundation counterpart, [CFRunLoopTimerRef](apple-reference-documentation://hcPs68Qze5).

这段讲述了Timer并不是十分精确的,他需要依赖runloop的处理事件循环周期,所以有一个容忍的公差度

NSTimer是如何造成的当前VC无法释放的

1587377415988.jpg

如上图所示,当你使用下列方式Target-Action初始话的时候,造成的当前self无法释放,原因当前的NSRunLoop强持有的Timer,而timer又被self强持有,所以Timer无法释放,self也无法释放,

self.timer = [NSTimer timerWithTimeInterval:1 target:self selector:@selector(selector) userInfo:id repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSDefaultRunLoopMode];

如何解决上边这种方法定义的Timer呢?就是我需要调用的【self.timer invalidate】方法,下面我们看下官方文档给出的解释


1587377506737.jpg

特别是图中的标出来的这句,意思是当你调用invalidate的时候,会打破runLoop强引用Timer,也同时打破Taget对Timer的强持有,这个时候 在看(1587377415988.jpg)此图剩余self对timer单向强持有了,所谓的循环引用也就被打破了,所以打破的重重之重是需要调用invalidate方法。

总结:

当我们使用初始化方式是Target-Action这种方式的初始化确实是存在循环引用的也就是下列的方法


需要代码添加到当前的RunLoop中
+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)ti invocation:(NSInvocation *)invocation repeats:(BOOL)yesOrNo;
系统自己给你添加到当前的RunLoop中并且是Defalut Mode模式 参考API的注释
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti invocation:(NSInvocation *)invocation repeats:(BOOL)yesOrNo;
需要代码添加到当前的RunLoop中
+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo;
系统自己给你添加到当前的RunLoop中并且是Defalut Mode模式 参考API的注释
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo;
第二种初始化 使用了带有Block的事件回调函数进行的处理,初始化函数,可能苹果意思到定时器存在的循环引用的缺陷,在iOS 10系统以后使用了这种Block回调的 就不存在循环引用了,此时也可以再dealloc函数中调用 invalidate 函数就不会存Target-Action 这种形式初始化的定时器的无法调用的问题了
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)interval repeats:(BOOL)repeats block:(void (^)(NSTimer *timer))block API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0));

/// Initializes a new NSTimer object using the block as the main body of execution for the timer. This timer needs to be scheduled on a run loop (via -[NSRunLoop addTimer:]) before it will fire.
/// - parameter:  fireDate   The time at which the timer should first fire.
/// - parameter:  interval  The number of seconds between firings of the timer. If seconds is less than or equal to 0.0, this method chooses the nonnegative value of 0.1 milliseconds instead
/// - parameter:  repeats  If YES, the timer will repeatedly reschedule itself until invalidated. If NO, the timer will be invalidated after it fires.
/// - parameter:  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
- (instancetype)initWithFireDate:(NSDate *)date interval:(NSTimeInterval)interval repeats:(BOOL)repeats block:(void (^)(NSTimer *timer))block API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0));

介绍完循环引用的问题了,我们看下使用Taregt-Action 这种形式,我们一般是需要选择合适的时机去调用invalidate解除下循环引用让定时器无效,但是这个时机的选择比较不确定,需要根据具体的业务需求如pop出页面的时候,dismiss调页面时候。主动调用invalidate函数
所以我们不想管他什么时机调用,我们不想手动调用了,我们只关注当前VC释放的时候定时器就无效掉,于是我们需要借助一个中间类去帮我们自动实现这种需求

类方法:

创建一个继承NSObject的子类TempTarget,并创建开启计时器的方法。

TempTarget .h文件

@property (nonatomic, assign) SEL selector;
@property (nonatomic, weak) NSTimer *tempTimer;
@property (nonatomic, weak) id tempTarget;

+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)interval target:(id)tempTarget selector:(SEL)selector userInfo:(id)userInfo repeats:(BOOL)repeats;

TempTarget .m文件

@implementation TempTarget

+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)interval target:(id)tempTarget selector:(SEL)selector userInfo:(id)userInfo repeats:(BOOL)repeats {
    TempTarget *target = [[TempTarget alloc] init];
    target.tempTarget = tempTarget;
    target.selector = selector;
    target.tempTimer = [NSTimer scheduledTimerWithTimeInterval:interval target:target selector:@selector(timerSelector:) userInfo:userInfo repeats:repeats];
    return target.tempTimer;
}

- (void)timerSelector:(NSTimer *)tempTimer {
    if (self.tempTarget && [self.tempTarget respondsToSelector:self.selector]) {
        [self.tempTarget performSelector:self.selector withObject:tempTimer.userInfo];
    }else {
        [self.tempTimer invalidate];
    }
}

@end

VC调用

self.timer = [TempTarget scheduledTimerWithTimeInterval:1 target:self selector:@selector(timerStart:) userInfo:nil repeats:YES];
注意

VC强引用timer,因为timer的target是TempTarget实例,所以timer强引用TempTarget实例,而TempTarget实例弱引用VC,解除循环引用。看TempTarget.h 文件两个所引用属性,其实这种方法本质还是存在循环医用的问题的,根本解决循环引用还是主动调用invalidate方法

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

推荐阅读更多精彩内容