自欺欺人的使用 NSTimer 销毁

自欺欺人的使用 NSTimer 销毁

Demo地址

1.NSTimer是要加到runloop中才会起作用。

常见的创建timer方式
// 第一种方式
@property (nonatomic , strong) NSTimer *timer;

// 默认加入当前runloop的NSDefaultRunLoopMode
self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(timerAction:) userInfo:nil repeats:NO];
// 第二种方式
self.timer = [NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(timerAction:) userInfo:nil repeats:YES];
  • 第一种缺陷如果当前线程就是主线程,也就是UI线程时,某些UI事件,比如UIScrollView的拖动操作,会将Run Loop切换成NSEventTrackingRunLoopMode模式,在这个过程中,默认的NSDefaultRunLoopMode模式中注册的事件是不会被执行的。也就是说,此时使用scheduledTimerWithTimeInterval添加到Run Loop中的Timer就不会执行
  • 第二种方式需要使用NSRunLoop的addTimer:forMode:方法来把Timer按照指定模式加入到Run Loop中。这里使用的模式是:NSRunLoopCommonModes,这个模式等效于NSDefaultRunLoopModeNSEventTrackingRunLoopMode的结合
[[NSRunLoop mainRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];
  • 以上两种方式都是在主线程上创建的,如果在子线程创建的timer,加入到runloop则需要手动开启runloop[[NSRunLoop currentRunLoop] run];,同时也必须在子线程销毁。

2.NSTimer会强引用它的target对象。

  • [self.timer invalidate]是唯一的方法将定时器从循环池中移除
- (void)dealloc
{
    // 自欺欺人的写法,永远都不会执行到,除非你在外部手动invalidate这个timer
    [self.timer invalidate];
}
  • 当我们在控制器中创建timer且tager设为self时。
  • 会发生 timer 添加到 Runloop 的时候,且会被 Runloop 强引用,
    • Note in particular that 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.
  • 然后 Timer 又会有一个对 Target 的强引用(也就是 self )
    • Target is the object to which to send the message specified by aSelector when the timer fires. The timer maintains a strong reference to target until it (the timer) is invalidated.
  • 也就是说 NSTimer 强引用了 self ,self的全局变量 NSTimer 又使 self 强引用了 NSTimer,导致 self 一直不能被释放掉,所以也就走不到 self 的 dealloc 里。
  • 此时我们就会想把 Target 设置为 weakSelf ,运行后也不起作用. 是由于我们的 self 和 weakSelf 都是指针指向控制器,控制器的dealloc需要timer的销毁才调用。同样造成相互强引用。 __weak typeof(self) weakSelf = self; self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:weakSelf selector:@selector(timerAction:) userInfo:nil repeats:NO];
  • 此时我们又想到下面那种把timer设为weak,此时是直接运行造成坏内存访问,因为timer创建就销毁
@property (nonatomic , strong) NSTimer *timer;
self.timer = [NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(timerAction:) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];
  • 另一种当我们在创建完timer后 置为nil,NSTimer还会不会起作用,答案是会起作用。因为Runloop对NSTimer 有了强引用,指向NSTimer那块内存。
self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(timerAction) userInfo:nil repeats:YES];
self.timer = nil;

3.解决runloop循环引用

  • iOS 10.0 以后增加两个创建方法
+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)interval repeats:(BOOL)repeats block:(void (^)(NSTimer *timer))block API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0));

+ (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));
自定义分类创建NSTimer,适用于iOS 10以前
  • 原理等同于以上方法,把 target 转换为 NSTimer 自身然后把控制器的定时器方法在block方法中保存执行。
+ (instancetype)syl_timerWithTimeInterval:(NSTimeInterval)time repeats:(BOOL)repeats block:(void (^)(NSTimer *timer))block
{
    // 此时的 target 为 NSTimer
    return [NSTimer timerWithTimeInterval:time target:self selector:@selector(timeAction:) userInfo:block repeats:repeats];
}

+ (void)timeAction:(NSTimer *)timer {

    void (^block)(NSTimer *) = [timer userInfo];

    !block?:block(timer);
}

4.使用 GCD 定时器

// GCD 定时器
- (void)timerNine {
    __weak typeof(self) weakSelf = self;
    dispatch_queue_t queue = dispatch_queue_create("SYLingGCDTimer", DISPATCH_QUEUE_CONCURRENT);
    dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
    // leewayInSeconds 精准度
    dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, 1.0 * NSEC_PER_SEC, 0 * NSEC_PER_SEC);
    dispatch_source_set_event_handler(timer, ^{
        // code to be executed when timer fires
        timer;
        [weakSelf timerAction];
    });
    dispatch_resume(timer);
}

Demo地址

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏码匠的流水账

springboot2上报metrics到statsd

micrometer-registry-statsd-1.0.1-sources.jar!/io/micrometer/statsd/StatsdFlavor....

651
来自专栏一枝花算不算浪漫

mybatis结合分页的使用及解析.

3477
来自专栏码匠的流水账

Java10的新特性

上面列出的是大方面的特性,除此之外还有一些api的更新及废弃,主要见What’s New in JDK 10 - New Features and Enhanc...

300
来自专栏IT笔记

JAR版本升级你所遇到的坑!!!

JSON-lib包是一个beans,collections,maps,java arrays 和XML和JSON互相转换的包。 然而,这家伙在2010年出了一个...

3186
来自专栏Java成神之路

Java企业微信开发_08_素材管理之下载微信临时素材到本地服务器

请求地址:https://qyapi.weixin.qq.com/cgi-bin/media/get?access_token=ACCESS_TOKEN&med...

762
来自专栏函数式编程语言及工具

Akka(15): 持久化模式:AtLeastOnceDelivery-消息保证送达模式

  消息保证送达是指消息发送方保证在任何情况下都会至少一次确定的消息送达。AtleastOnceDelivery是一个独立的trait,主要作用是对不确定已送达...

2525
来自专栏流媒体

Android RTMP推流之MediaCodec硬编码一(H.264进行flv封装)

在前面Android平台下使用FFmpeg进行RTMP推流(摄像头推流)的文章中,介绍了如何使用FFmpeg进行H264编码和Rtmp推流。接下来讲分几篇文章来...

1773
来自专栏非著名程序员

Retrofit OKHttp 教你怎么持久化管理Cookie

? 投稿作者:黄海杰 原文链接: http://blog.csdn.net/lyhhj/article/details/51345386 绪论 最近小编有点...

35710
来自专栏iOS开发笔记

iOS学习系列之多线程—NSThread(一)

iOS线程模型 1 NSThread:objective-c线程库 2 Blocks/GCD: Blocks模式的线程池 3 NSOperationQueue:...

2705
来自专栏郭霖

Android Context完全解析,你所不知道的Context的各种细节

前几篇文章,我也是费劲心思写了一个ListView系列的三部曲,虽然在内容上可以说是绝对的精华,但是很多朋友都表示看不懂。好吧,这个系列不仅是把大家给难倒了,也...

2339

扫码关注云+社区