iOS中定时器有三种,分别是NSTimer、CADisplayLink、dispatch_source,下面就分别对这三种计时器进行说明。

一、NSTimer

NSTimer这种定时器用的比较多,但是特别需要注意释放问题,如果处理不好很容易引起循环引用问题,造成内存泄漏。

1.1 NSTimer的创建

NSTimer有两种创建方法。

方法一:

这种方法虽然创建了NSTimer,但是定时器却没有起作用。这种方式创建的NSTimer,需要加入到NSRunLoop中,有NSRunLoop的驱动才会让定时器跑起来。

self.timer = [[NSTimer alloc] initWithFireDate:[NSDate dateWithTimeIntervalSinceNow:1] interval:1 target:self selector:@selector(timeEvent) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];

方法二:

这种方法创建的NSTimer不需要加入到NSRunLoop中就能跑起来,因为创建了定时器之后,系统默认会把定时器加到RunLoop中,不需要我们自己手动加了。

self.timer = [NSTimer scheduledTimerWithTimeInterval:3.0 target:self selector:@selector(timeEvent) userInfo:nil repeats:YES];

1.2 NSTimer的释放

- (void)dealloc
{[self.timer invalidate];self.timer = nil;NSLog(@"dealloc...");
}

通常这样写其实是有问题的,由于self强引用了timer,同时timer也强引用了self,所以循环引用造成dealloc方法根本不会走,self和timer都不会被释放,造成内存泄漏。

二、循环引用的几种解决办法

3.1 使用invalidate结束timer运行

我们第一时间肯定想到的是[self.timer invalidate]不就可以了吗,当然这是正确的思路,那么我们调用时机是什么呢?viewWillDisAppear还是viewDidDisAppear?实际上在我们实际操作中,如果当前页面有push操作的话,当前页面还在栈里面,这时候我们释放timer肯定是错误的,所以这时候我们可以用到下面的方法:

- (void)didMoveToParentViewController:(UIViewController *)parent {// 无论push 进来 还是 pop 出去 正常运行// 就算继续push 到下一层 pop 回去还是继续if (parent == nil) {[self.timer invalidate];self.timer = nil;NSLog(@"timer销毁");}
}

3.2 中介者模式

换个思路,timer会造成循环引用是因为target强持有了self,造成的循环引用,那我们是否可以包装一下target,使得timer绑定另外一个不是self的target对象来打破这层强持有关系。

@interface HSTimer : NSObject+ (NSTimer *) scheduledTimerWithTimeInterval:(NSTimeInterval)intervaltarget:(id)aTargetselector:(SEL)aSelectoruserInfo:(id)userInforepeats:(BOOL)repeats;
@end
#import "HSTimer.h"@interface HSTimerTarget : NSObject
@property (nonatomic, weak) id target;
@property (nonatomic, assign) SEL selector;
@property (nonatomic, weak) NSTimer* timer;
@end@implementation HSTimerTarget
- (void)timeAction:(NSTimer *)timer {if(self.target) {[self.target performSelector:self.selector withObject:timer.userInfo afterDelay:0.0f];} else {[self.timer invalidate];}
}
@end@implementation HSTimer
+ (NSTimer *) scheduledTimerWithTimeInterval:(NSTimeInterval)intervaltarget:(id)aTargetselector:(SEL)aSelectoruserInfo:(id)userInfo repeats:(BOOL)repeats {HSTimerTarget* timerTarget = [[HSTimerTarget alloc] init];timerTarget.target = aTarget;timerTarget.selector = aSelector;timerTarget.timer = [NSTimer scheduledTimerWithTimeInterval:intervaltarget:timerTargetselector:@selector(timeAction:)userInfo:userInforepeats:repeats];return timerTarget.timer;
}
@end

使用:

#import "HSTimer.h"@property(nonatomic, strong) NSTimer *timer;self.timer = [HSTimer scheduledTimerWithTimeInterval:3.0 target:self selector:@selector(timeEvent) userInfo:nil repeats:YES];

3.3 NSProxy虚基类的方式

NSProxy是一个虚基类,它的地位等同于NSObject。我们不用self来响应timer方法的target,而是用NSProxy来响应。

HSProxy.h

@interface HSProxy : NSObject
+ (instancetype)proxyWithTransformObject:(id)object;
@end

HSProxy.m

#import "HSProxy.h"@interface HSProxy ()
@property (nonatomic, weak) id object;
@end@implementation HSProxy+ (instancetype)proxyWithTransformObject:(id)object {HSProxy *proxy = [HSProxy alloc];proxy.object = object; // 我们拿到外边的self,weak弱引用持有return proxy;
}
// 仅仅添加了weak类型的属性还不够,为了保证中间件能够响应外部self的事件,需要通过消息转发机制,让实际的响应target还是外部self,这一步至关重要,主要涉及到runtime的消息机制。
// proxy虚基类并没有持有vc,而是消息的转发,又给了vc
- (id)forwardingTargetForSelector:(SEL)aSelector {return self.object;
}@end

使用

#import "HSProxy.h"@property(nonatomic, strong) HSProxy *proxy;self.proxy = [HSProxy proxyWithTransformObject:self];
self.timer = [NSTimer scheduledTimerWithTimeInterval:3.0 target:self.proxy selector:@selector(timeEvent) userInfo:nil repeats:YES];

三、拓展

3.1 能不能用weakSelf打破循环引用?

 __weak typeof(self) weakSelf = self;
self.timer = [NSTimer scheduledTimerWithTimeInterval:3.0 target:weakSelf selector:@selector(timeEvent) userInfo:nil repeats:YES];

答案:不能。

weakSelf 和 self 虽然指针地址不一样,但是都是指向当前的vc,也就是说这两者指向的内存地址其实是一样的,所以传入target里面的其实还是当前的vc,这样还是不能打破循环引用。

3.2 滑动tableView或者scrollView会暂停NSTimer

在同一个ViewController里面创建NSTimer和tableView或者scrollView,当滑动tableView或者scrollView的时候,会发现NSTimer会暂停,当tableView或者scrollView停止之后,NSTimer又会继续运行。这是为什么呢?

RunLoop在不同的情况下,运行的模式不一样:

在viewDidApperar()方法中打印的Current Mode为:kCFRunLoopDefaultMode在scrollViewDidScroll()方法中打印的Current Mode为:UITrackingRunLoopMode

因为NSTimer对象默认添加到了当前RunLoop的DefaultMode中,而在切换成TrackingRunLoopMode时,定时器就会停止工作。

解决该问题最直接方法是,将NSTimer在TrackingRunLoopMode中也添加一份。这样的话无论是在 DefaultMode还是TrackingRunLoopMode中,定时器都会正常的工作。

如果你对RunLoop比较熟悉的话,可以知道CommonModes就是DefaultModeTrackingRunLoopMode的集合,所以我们只需要将NSTimer对象与当前线程所对应的RunLoop中的CommonModes关联即可。

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

3.3 NSTimer精度不高

NSTimer计时的精度不是很高,但是项目中一般的需求还是能满足,如果需要更高的计时精度就需要用CADisplayLink。

iOS NSTimer定时器相关推荐

  1. IOS的pch文件,NSTimer定时器,运行消息循环,随机色使用

    IOS的pch文件,NSTimer定时器,运行消息循环,随机颜色获取使用 xcode新创建一个项目命名pchTest 右键创建文件,选择other 下面的pch,名字不要管默认就好 选中项目 : Bu ...

  2. iOS开发 NSTimer定时器

    目录 前言 NSTimer是什么 NSTimer怎么使用 创建NSTimer 触发NSTimer 销毁NSTimer NSTimer与runloop NSTimer与performSelector:w ...

  3. iOS中定时器NSTimer的开启与关闭

    调用一次计时器方法: [cpp] view plain copy   myTimer = [NSTimer scheduledTimerWithTimeInterval:1.5 target:self ...

  4. iOS中定时器NSTimer的使用

    1.初始化 + (NSTimer *)timerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelect ...

  5. ios nstimer实现延时_iOS中定时器NSTimer的使用

    1.初始化 + (NSTimer *)timerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelect ...

  6. IOS中定时器NSTimer

    调用一次计时器方法: [cpp]  view plain copy   myTimer = [NSTimer scheduledTimerWithTimeInterval:1.5 target:sel ...

  7. [iOS]-NSTimer与循环引用的理解

    目录: 参考的博客: 问题引入 循环引用 简单的循环引用 Block中的循环引用强弱共舞 Delegate中的循环引用 NSTimer 创建NSTimer 销毁NSTimer invalidate f ...

  8. ios nstimer实现延时_IOS_IOS开发代码分享之用nstimer实现倒计时功能,用nstimer实现倒计时功能,废话 - phpStudy...

    IOS开发代码分享之用nstimer实现倒计时功能 用nstimer实现倒计时功能,废话不多说,直接上代码,详细解释请参照注释 // [NSTimer scheduledTimerWithTimeIn ...

  9. NSTimer定时器进阶——详细介绍,循环引用分析与解决

    引言 定时器:A timer waits until a certain time interval has elapsed and then fires, sending a specified m ...

最新文章

  1. c++, 派生类的构造函数和析构函数 , [ 以及operator=不能被继承 or Not的探讨]
  2. 计算机基础-计算机硬件
  3. 2016各大公司校招薪水曝光:年薪28万,这只是零花钱
  4. 【BZOJ3994】[SDOI2015]约数个数和 莫比乌斯反演
  5. svn版本库浏览器_svn:版本库xxx不存在||svn:No such revision xxx的问题
  6. 三星三层影像传感器提升拍摄能力 索尼压力倍增
  7. Linux下oracle11g 导入导出操作详细
  8. 研发协同平台持续集成Jenkins作业设计演进
  9. 两次结果的绝对差值_你知道电子天平的检定和检定结果的影响因素有哪些吗?...
  10. less入门及基础学习(建议有css基础)
  11. android 前后同时预览_用上这些官方动态壁纸,让你的 Android 主屏简洁又优雅
  12. Loadrunner破解版安装
  13. 地理空间数据云下载的dem数据(xxxdem.tif格式)中的栅格大小从度(0.000002)改成米(30)
  14. 2021年施工升降机司机(建筑特殊工种)考试题及施工升降机司机(建筑特殊工种)找解析
  15. 『原创』统计建模与R软件-第四章 参数估计
  16. 自然语言处理:CBOW(哈夫曼树)与Skip-Gram模型
  17. 给SwipeRefreshLayout添加上拉加载更多功能
  18. 在html页面中引入jquery
  19. TI公司Tina-ti和FilterProDesktop下载地址
  20. 广东财经大学理工科毕业论文word模版

热门文章

  1. idea怎么样打开introduce local variable
  2. Linux Gadget的一点研究之U盘和USB虚拟串口
  3. fdisk 挂载虚拟硬盘
  4. C#C#textbox设置滚动条
  5. bwl老二吃嘲讽吗_魔兽世界怀旧服,4DK嘲讽抵抗了,到底需不需要一顿猛如虎的操作来应急?...
  6. ubuntu20.04双系统安装及ROS安装
  7. java实现各种数据统计图(柱形图,饼图,折线图)
  8. codeblocks中文乱码问题
  9. 暖暖的短消息(集锦)
  10. word2016,分成两栏后文字顺序混乱的问题