NSTimer之前我真不懂

NSTimer不就是定时器吗,这个平时经常用的,

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            self->startTimer = [NSTimer scheduledTimerWithTimeInterval:self.time target:self selector:@selector(start:) userInfo:nil repeats:YES];
            [[NSRunLoop currentRunLoop] run];
        });

这样写之后不做其他处理的同学断点下dealloc方法,你会情不自禁的说三句“我擦、我擦、我擦”。(😄)

NSTimer导致的内存泄漏

少年郎之前一直都是这样用NSTimer,在排查一个页面内存泄漏的时候发现VC pop之后dealloc死活不走,该弱化的对象已经弱化,该处理的方法已经处理,即便是所有成员变量的引用计数也都一一排查确定正常之后dealloc方法还是不走,最后只能向一直认为没有问题的定时器下手,问题居然还真他妈的出现在这。向之前写的项目默哀半分钟,之前用错你了😢。
查过资料了解到,为了保证参数的生命周期,NSTimer会对target对象retain一次,做强引用。以保证即便target销毁了,定时器还能正常调用timeEvent。因为定时器要加到RunLoop中,所以RunLoop强引用着NSTimer,一般情况下你的target就是当前的控制器,如果你想让控制器如你所愿的销毁了,首先得销毁NSTimer。不然NSTimer强引用着self,self就无法销毁,从而导致内存泄漏。

销毁定时器的方法

[startTimer invalidate];
 startTimer = nil;

timer被schedule的时候,timer会持有target对象,NSRunLoop对象会持有timer。当invalidate被调用时,NSRunLoop对象会释放对timer的持有,timer会释放对target的持有。除此之外,没有途径可以释放timer对target的持有。所以解决内存泄露就必须撤销timer,若不撤销,target对象将永远无法释放。

销毁timer

如果可以的话在viewWillDisappear方法里销毁timer,或者点击返回按钮的时候销毁timer,不过这些都是局限性。如果要再push一个VC,视图消失的时候销毁timer就会有问题,返回按钮的方法里销毁还要考虑左滑手势...,这些感觉都不太“科学”。
看到有大佬把target特殊处理成不是当前控制器,timer就不再强引用self,dealloc方法自然就能正常调用。

- (void)dealloc
{
    [startTimer  invalidate];
    startTimer = nil;
}

具体实现代码

#import <Foundation/Foundation.h>

typedef void (^WXWTimerBlock)(id userInfo);

@interface WXWTimer : NSObject

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

+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)interval
                                      block:(WXWTimerBlock)block
                                   userInfo:(id)userInfo
                                    repeats:(BOOL)repeats;


@end

@interface WXWTimerTarget : NSObject

@property (nonatomic, weak) id target;
@property (nonatomic, assign) SEL selector;
@property (nonatomic, weak) NSTimer* timer;

@end

@implementation WXWTimerTarget

- (void)timeAction:(NSTimer *)timer {
    if(self.target) {
        
        [self.target performSelector:self.selector withObject:timer.userInfo afterDelay:0.0f];
        
    } else {
        [self.timer invalidate];
    }
}

@end


@implementation WXWTimer

+ (NSTimer *) scheduledTimerWithTimeInterval:(NSTimeInterval)interval
                                      target:(id)aTarget
                                    selector:(SEL)aSelector
                                    userInfo:(id)userInfo
                                     repeats:(BOOL)repeats {
    WXWTimerTarget* timerTarget = [[WXWTimerTarget alloc] init];
    timerTarget.target = aTarget;
    timerTarget.selector = aSelector;
    timerTarget.timer = [NSTimer scheduledTimerWithTimeInterval:interval
                                                         target:timerTarget
                                                       selector:@selector(timeAction:)
                                                       userInfo:userInfo
                                                        repeats:repeats];
    return timerTarget.timer;
}

+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)interval
                                      block:(WXWTimerBlock)block
                                   userInfo:(id)userInfo
                                    repeats:(BOOL)repeats {
    NSMutableArray *userInfoArray = [NSMutableArray arrayWithObject:[block copy]];
    if (userInfo != nil) {
        [userInfoArray addObject:userInfo];
    }
    return [self scheduledTimerWithTimeInterval:interval
                                         target:self
                                       selector:@selector(timerBlock:)
                                       userInfo:[userInfoArray copy]
                                        repeats:repeats];
}

+ (void)timerBlock:(NSArray*)userInfo {
    WXWTimerBlock block = userInfo[0];
    id info = nil;
    if (userInfo.count == 2) {
        info = userInfo[1];
    }
    
    if (block) {
        block(info);
    }
}


@end

NSTimer与RunLoop的关系

之前写了个类似淘宝的消息滚动控件。本来也没有注意到这个问题,换工作面试的时候面试官看到这个问我怎么实现的,直接回答说:“用NSTimer加个定时器,设置重复滚动就可以了”。面试官笑了笑没说什么。场面一度十分尴尬。回来之后查了下才知道人家想要的回答应该是下面这样的,而不是我认为的那样😂。

NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(timeEvent) userInfo:nil repeats:YES];
    [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];

苹果加入RunLoop的默认的模式是NSDefaultRunLoopMode,当你滑动scrollView的时候runloop将会切换到UITrackingRunLoopMode、在加入的时候设置NSRunLoopCommonModes模式。这样所有模式都可以工作了。

非主线程中创建定时器需要让定时器执行

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
      
      NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(timeEvent) userInfo:nil repeats:YES];
       [[NSRunLoop currentRunLoop] run];
       
   });

RunLoop用来循环处理响应事件,每个线程都有一个RunLoop,苹果不允许自己创建RunLoop, 而且只有主线程的RunLoop是默认打开的,其他线程的RunLoop如果需要使用就必须手动打开。scheduledTimerWithTimeInterval这个方法创建好NSTimer以后会自动将它添加到当前线程的RunLoop,非主线程中只有调用run方法定时器才能开始工作

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

推荐阅读更多精彩内容

  • 说明iOS中的RunLoop使用场景1.保持线程的存活,而不是线性的执行完任务就退出了<1>不开启RunLoop的...
    野生塔塔酱阅读 6,786评论 15 109
  • 之前要做一个发送短信验证码的倒计时功能,打算用NSTimer来实现,做的过程中发现坑还是有不少的。 基本使用 NS...
    WeiHing阅读 4,377评论 1 8
  • 1. 什么是NSTimer   官方的解释“A timer provides a way to perform a...
    Timir阅读 766评论 0 3
  • iOS刨根问底-深入理解RunLoop 2017-05-08 10:35 by KenshinCui 概述 Run...
    mengjz阅读 1,554评论 1 10
  • 你看,扑空了吧,说你你还不听,哼!
    染可可阅读 183评论 1 1