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 ...
随机推荐
- winform的tab跳到下一个
先设置TabStop=true,再设置TabIndex的顺序
- C#实现录制屏幕
以前写过两篇录制麦克风语音和摄像头视频的文章(实现语音视频录制.在服务器端录制语音视频),最近有朋友问,如果要实现屏幕录制这样的功能,该怎么做了?实际上原理是差不多的,如果了解了我前面两篇文章中介绍的 ...
- linux配置oracle11G监听及本地网络服务 及 数据库建库
配置监听及本地网络服务 在oracle用户的图形界面oracle用户中,新开启一个终端,输入命令netca 会弹出如下界面. 数据库建库 在oracle用户的图形界面oracle用户中,新开启一个终端 ...
- 20145212 《Java程序设计》第1周学习总结
20145212 <Java程序设计>第1周学习总结 教材学习内容总结 看了毕向东老师的视频,我对Java有了进一步的了解.相比于其他的计算机编程语言(比如C语言),Java有一大特点就是 ...
- 浏览器本地存储(browser-storage,HTML5-localStorage > IE-UserData > Cookie)
https://www.baidufe.com/component/browser-storage/index.html BrowserStorage是浏览器本地存储的一个解决方案,存储优先级依次为: ...
- Mysql 与 Python socket
py1.py # -*- coding: utf-8 -*- import sqlalchemy import tushare import pandas import socket import s ...
- ecshop上传图片2
html代码 <tr> <td class="label">{$lang.lab_picture}</td> <td> <in ...
- ASP.NET技巧:教你制做Web实时进度条
网上已经有很多Web进度条的例子,但是很多都是估算时间,不能正真反应任务的真实进度.我自己结合多线程和ShowModalDialog制做了 一个实时进度条,原理很简单:使用线程开始长时间的任务,定义一 ...
- Open Yale course:Listening to Music
一.Introductionhttps://app.yinxiang.com /Home.action?offer=www_menu#n=4b034a29-986d-4914-8220-eb99c2e ...
- p命名空间的使用(不推荐用)
xmlns:p="http://www.springframework.org/schema/p" p:没有xsd文件,直接加上面那句就好了 <!-- singleton和p ...