iOS开发——高级篇——iOS 中的 NSTimer
以前的老代码在使用 NSTimer 时出现了内存泄露
NSTimer
fire
我们先用 NSTimer 来做个简单的计时器,每隔5秒钟在控制台输出 Fire 。比较想当然的做法是这样的:
| 
 1 
2 
3 
4 
5 
6 
7 
8 
9 
10 
11 
12 
13 
14 
15 
16 
 | 
@interface DetailViewController ()@property (nonatomic, weak) NSTimer *timer;@end@implementation DetailViewController- (IBAction)fireButtonPressed:(id)sender {    _timer = [NSTimer scheduledTimerWithTimeInterval:3.0f                                              target:self                                            selector:@selector(timerFire:)                                            userInfo:nil                                             repeats:YES];    [_timer fire];}-(void)timerFire:(id)userinfo {    NSLog(@"Fire");}@end | 
运行之后确实在控制台每隔3秒钟输出一次 Fire ,然而当我们从这个界面跳转到其他界面的时候却发现:控制台还在源源不断的输出着 Fire 。看来 Timer 并没有停止。
invalidate
既然没有停止,那我们在 DemoViewController 的 dealloc 里加上 invalidate 的方法:
| 
 1 
2 
3 
4 
 | 
-(void)dealloc {    [_timer invalidate];    NSLog(@"%@ dealloc", NSStringFromClass([self class]));} | 
再次运行,还是没有停止。原因是 Timer 添加到 Runloop 的时候,会被 Runloop 强引用:
| 
 1 
 | 
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 ):
| 
 1 
 | 
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 一直不能被释放掉,所以也就走不到 self 的 dealloc 里。
既然如此,那我们可以再加个 invalidate 按钮:
| 
 1 
2 
3 
 | 
- (IBAction)invalidateButtonPressed:(id)sender {    [_timer invalidate];} | 
嗯这样就可以了。(在 SOF 上有人说该在 invalidate 之后执行 _timer = nil ,未能理解为什么,如果你知道原因可以告诉我:)
在 invalidate 方法的文档里还有这这样一段话:
You must send this message from the thread on which the timer was installed. If you send this message from another thread, the input source associated with the timer may not be removed from its run loop, which could prevent the thread from exiting properly.
NSTimer 在哪个线程创建就要在哪个线程停止,否则会导致资源不能被正确的释放。看起来各种坑还不少。
dealloc
那么问题来了:如果我就是想让这个 NSTimer 一直输出,直到 DemoViewController 销毁了才停止,我该如何让它停止呢?
NSTimer 被 Runloop 强引用了,如果要释放就要调用 invalidate 方法。
但是我想在 DemoViewController 的 dealloc 里调用 invalidate 方法,但是 self 被 NSTimer 强引用了。
所以我还是要释放 NSTimer 先,然而不调用 invalidate 方法就不能释放它。
然而你不进入到 dealloc 方法里我又不能调用 invalidate 方法。
嗯…
HWWeakTimer
weakSelf
问题的关键就在于 self 被 NSTimer 强引用了,如果我们能打破这个强引用问题自然而然就解决了。所以一个很简单的想法就是:weakSelf:
| 
 1 
2 
3 
4 
5 
6 
 | 
__weak typeof(self) weakSelf = self;_timer = [NSTimer scheduledTimerWithTimeInterval:3.0f                                          target:weakSelf                                        selector:@selector(timerFire:)                                        userInfo:nil                                         repeats:YES]; | 
然而这并没有什么卵用,这里的 __weak 和 __strong 唯一的区别就是:如果在这两行代码执行的期间 self 被释放了, NSTimer 的 target 会变成 nil 。
target
既然没办法通过 __weak 把 self 抽离出来,我们可以造个假的 target 给 NSTimer 。这个假的 target 类似于一个中间的代理人,它做的唯一的工作就是挺身而出接下了 NSTimer 的强引用。类声明如下:
| 
 1 
2 
3 
4 
5 
6 
7 
8 
9 
10 
11 
12 
13 
14 
 | 
@interface HWWeakTimerTarget : NSObject@property (nonatomic, weak) id target;@property (nonatomic, assign) SEL selector;@property (nonatomic, weak) NSTimer* timer;@end@implementation HWWeakTimerTarget- (void) fire:(NSTimer *)timer {    if(self.target) {        [self.target performSelector:self.selector withObject:timer.userInfo];    } else {        [self.timer invalidate];    }}@end | 
然后我们再封装个假的 scheduledTimerWithTimeInterval 方法,但是在调用的时候已经偷梁换柱了:
| 
 1 
2 
3 
4 
5 
6 
7 
8 
9 
10 
11 
12 
13 
14 
15 
 | 
+ (NSTimer *) scheduledTimerWithTimeInterval:(NSTimeInterval)interval                                      target:(id)aTarget                                    selector:(SEL)aSelector                                    userInfo:(id)userInfo                                     repeats:(BOOL)repeats {    HWWeakTimerTarget* timerTarget = [[HWWeakTimerTarget alloc] init];    timerTarget.target = aTarget;    timerTarget.selector = aSelector;    timerTarget.timer = [NSTimer scheduledTimerWithTimeInterval:interval                                                         target:timerTarget                                                       selector:@selector(fire:)                                                       userInfo:userInfo                                                        repeats:repeats];    return timerTarget.timer;} | 
再次运行,问题解决。
block
如果能用 block 来调用 NSTimer 那岂不是更好了。我们可以这样来实现:
| 
 1 
2 
3 
4 
5 
6 
7 
8 
9 
10 
11 
12 
13 
14 
15 
16 
17 
18 
 | 
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)interval                                      block:(HWTimerHandler)block                                   userInfo:(id)userInfo                                    repeats:(BOOL)repeats {    return [self scheduledTimerWithTimeInterval:interval                                         target:self                                       selector:@selector(_timerBlockInvoke:)                                       userInfo:@[[block copy], userInfo]                                        repeats:repeats];}+ (void)_timerBlockInvoke:(NSArray*)userInfo {    HWTimerHandler block = userInfo[0];    id info = userInfo[1];    // or `!block ?: block();` @sunnyxx    if (block) {        block(info);    }} | 
这样我们就可以直接在 block 里写相关逻辑了:
| 
 1 
2 
3 
4 
5 
6 
 | 
- (IBAction)fireButtonPressed:(id)sender {    _timer = [HWWeakTimer scheduledTimerWithTimeInterval:3.0f block:^(id userInfo) {        NSLog(@"%@", userInfo);    } userInfo:@"Fire" repeats:YES];    [_timer fire];} | 
嗯就是这样。
iOS开发——高级篇——iOS 中的 NSTimer的更多相关文章
- iOS开发——高级篇——iOS开发之网络安全密码学
		
一.非对称加密 - RSA : + 公钥加密,私钥解密: + 私钥加密,公钥解密: + 只能通过因式分解来破解 二.对称加密 - DES - 3DES - AES (高级密码标准,美国国家安全局使用, ...
 - iOS开发——高级篇——iOS 项目的目录结构
		
最近闲来无事去面试一下iOS开发,让我感到吃惊的,面试官竟然问怎么分目录结构,还具体问每个子目录的文件名. 目录结构确实非常重要,面试官这么问,无疑是想窥探开发经验.清晰的目录结构,可让人一眼明白相应 ...
 - iOS开发——高级篇——iOS中如何选择delegate、通知、KVO(以及三者的区别)
		
在开发IOS应用的时候,我们会经常遇到一个常见的问题:在不过分耦合的前提下,controllers[B]怎么进行通信.在IOS应用不断的出现三种模式来实现这种通信:1委托delegation2通知 ...
 - iOS开发——高级篇——iOS中常见的设计模式(MVC/单例/委托/观察者)
		
关于设计模式这个问题,在网上也找过一些资料,下面是我自己总结的,分享给大家 如果你刚接触设计模式,我们有好消息告诉你!首先,多亏了Cocoa的构建方式,你已经使用了许多的设计模式以及被鼓励的最佳实践. ...
 - iOS开发——高级篇——iOS中为什么block用copy属性
		
1. Block的声明和线程安全Block属性的声明,首先需要用copy修饰符,因为只有copy后的Block才会在堆中,栈中的Block的生命周期是和栈绑定的,可以参考之前的文章(iOS: 非ARC ...
 - iOS开发——高级篇——iOS 强制退出程序APP代码
		
1.先po代码 UIAlertView* alert = [[UIAlertView alloc] initWithTitle:self.exitapplication message:@" ...
 - iOS开发——高级篇——iOS如何彻底避免数组越界
		
我们先来看看有可能会出现的数组越界Crash的地方: ? 1 2 3 4 5 6 7 - (void)tableView:(UITableView *)tableView didSelectRowAt ...
 - iOS开发——高级篇——iOS键盘的相关设置(UITextfield)
		
一.键盘风格 UIKit框架支持8种风格键盘. typedef enum { UIKeyboardTypeDefault, // 默认键盘:支持所有字符 UIKeyboardTypeASCIICapa ...
 - iOS开发——高级篇——iOS涂鸦画板效果实现
		
一个简单的绘图应用,模仿苹果自带软件备忘录里的涂鸦功能 核心代码 #import "DrawView.h" #import "DrawPath.h" @inte ...
 
随机推荐
- ionic的scroll的使用出现的问题
			
今天再写ionic的时候发现了一个问题,在使用ui-view的时候引用ion-scroll指令的时候初次加载的时候能能实现拖拽滚动,只能实现滚动鼠标滚轴滚动,然后各种调试,各种比较,终于发现原来是路由 ...
 - Linux 中,如何显示 (gcc)make时实际执行命令
			
问题: 调试编译问题,如何获取,GCC(或许make)时,实际编译器和链接器正在执行的命令? 解决方法: 方法一:通用方法 使用dry run,如下 $ make -n 这将显示make 命令正在试图 ...
 - WinForm------TextEdit控件去掉换行符
			
//将换行转为空格 string str = this.DetailEdit.Text.Replace("\r\n"," ");
 - jquery id选择器 id带"."问题
			
例如控件ID为user.id 使用$("#user.id")不能得到正确的结果 必须使用\\转义 即$("#user\\.id")
 - Eclipse学习笔记——快捷键
			
alt+/ 直接呼出要重写的方法 ctrl+1 快速修复(没导包,没抛出异常等等) ctrl+shift+o 导包 ctrl+shift+F 格式化代码块(自动给代码排版) alt+方向键 向前向后( ...
 - ansible加密命令
			
ansible-vault用于配置文件加密,如编写的playbook配置文件中包含敏感信息,不希望其他人随意查看,ansible-valut可加密/解密这个配置文件,刚试了下也可以加密txt文档,猜想 ...
 - localdb
			
<connectionStrings> - <add name="default" connectionString="Data Source=.; I ...
 - tomcat的文件路径  servelet的配置 以及maven中的WEB-INF的路径
			
Tomcat JavaWeb应用的组成结构 开发JavaWeb应用时,不同类型的文件有严格的存放规则,否则不仅可能会使web应用无法访问,还会导致web服务器启动报错 WebRoot →Web应用所在 ...
 - iOS-马上着手开发iOS应用应用程序-第一部分介绍
			
教程:基础 main 中的 main.m 函数会调用自动释放池 (autorelease pool) 中的 UIApplicationMain 函数. @autoreleasepool { retur ...
 - HBase中的压缩算法比较 GZIP、LZO、Zippy、Snappy [转]
			
网址: http://www.cnblogs.com/panfeng412/archive/2012/12/24/applications-scenario-summary-of-compressio ...