一、NSTimer使用
const NSTimeInterval TimeInterval = 1.0;
@interface UIViewController ()
// 定義屬性timer
@property (nonatomic, strong) NSTimer *timer;
@end
/**
* timer 初始化
* repeats:參數(shù)表示是否重復執(zhí)行(YES表示每TimeInterval秒運行一次function方法。NO表示不重復只調(diào)用 一次,timer運行一次就會自動停止運行)
*/
self.timer = [NSTimer scheduledTimerWithTimeInterval:TimeInterval target:self selector:@selector(fire) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];
注意:將計數(shù)器的repeats設置為YES的時候,self的引用計數(shù)會加1。因此可能會導致self(即VC)不能release,所以在UIViewController delloc前將計數(shù)器timer設置為失效,否則可能會導致內(nèi)存泄露。
//開啟定時器
[self.timer fire];
//暫停定時器(然后再某種情況下再次開啟運行timer)
self.timer.fireDate = [NSDate distantFuture];
//再次開啟定時器
self.timer.fireDate = [NSDate distantPast];
//取消定時器(這個是永久的停止)
[self.timer invalidate];
// 停止后,一定要將timer賦空,否則還是沒有釋放
self.timer = nil;
通常寫到這會遇到諸多問題。
二、NSTimer常見問題
當repeats為YES時timer出現(xiàn)無法釋放的問題(強引用,而非循環(huán)引用引起)
// runloop強引用timer,timer強引用self。如果timer不失效,self就不會釋放,進而造成內(nèi)存泄漏
runloop -> timer -> self(UIViewController)
解決辦法有4種:
也是最low的方法在控制器消失的時候(viewDidDisappear方法中)設置timer失效,但是會出現(xiàn)一些問題,跳轉下一級界面的時候timer失效,再返回的時候還得在界面出現(xiàn)(viewWillAppear方法中)時從新設置timer,比較繁瑣,控制不好還會問題。不推薦使用
在UIViewController中調(diào)用didMoveToParentViewController:方法設置timer失效
// 這種方法只有在進入VC時使用的push的方式進入才有效,present進入不會調(diào)用此方法
- (void)didMoveToParentViewController:(UIViewController *)parent{
if (!parent) {
[self.timer invalidate];
self.timer = nil;
}
}
- 利用消息轉發(fā)機制
//利用中間鍵target 不讓timer來強用self
runloop -> timer -> target
示例代碼:
@interface UIViewController ()
@property (nonatomic, strong) NSTimer *timer;
@property (nonatomic, strong) id target;
@end
- (void)creatTimer{
_target = [NSObject new];
//我們需要使用runtime來給_target添加方法,引入頭文件 #import <objc/runtime.h>
class_addMethod([_target class], @selector(fire), (IMP)fireIMP, "v@:");
//timer 的target直接指向_target
self.timer = [NSTimer scheduledTimerWithTimeInterval:TimeInterval target:_target selector:@selector(fire) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];
}
void fireIMP(id self, IMP _cmd) {
NSLog(@"重復跑起來");
}
// 這樣只需要在VC 的dealloc方法中設置timer失效即可
- (void)dealloc{
[self.timer invalidate];
self.timer = nil;
}
- 中間鍵弱引用self
// runloop強引用timer,timer強引用proxy, proxy弱引用self。(弱引用不會使self的引用計數(shù)加一)
runloop -> timer -> proxy --> self(UIViewController)
用一個比NSObjec更輕量級的類NSProxy來做中間鍵
示例代碼:
// 創(chuàng)建NSProxy類
#import <Foundation/Foundation.h>
@interface WeakProxy : NSProxy
//使用弱引用
@property (nonatomic, weak) id target;
@end
#import "WeakProxy.h"
@implementation WeakProxy
/**
* - (NSMethodSignature *)methodSignatureForSelector:(SEL)sel
* - (void)forwardInvocation:(NSInvocation *)invocation
* 這兩個方法必須寫
*/
// 方法簽名
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel{
return [self.target methodSignatureForSelector:sel];
}
// 消息轉發(fā)
- (void)forwardInvocation:(NSInvocation *)invocation{
[invocation invokeWithTarget:self.target];
}
@end
回到UIViewController中
- (void)creatTimer{
// 因為在上述類中沒有寫構造函數(shù)直接alloc。
WeakProxy *proxy = [WeakProxy alloc];
// 弱引用self
proxy.target = self;
//timer 的target直接指向proxy
self.timer = [NSTimer scheduledTimerWithTimeInterval:TimeInterval target:proxy selector:@selector(fire) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];
}
// 然后只需在delloc中實現(xiàn)計時器的銷毀即可
- (void)dealloc{
[self.timer invalidate];
self.timer = nil;
}
如果有什么問題請?zhí)岢觯餐懻摻鉀Q。