1. self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(timerFired) userInfo:nil repeats:YES];複製代碼
2. self.timer = [NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(timerFired) userInfo:nil repeats:YES];
[[NSRunLoop mainRunLoop] addTimer:self.timer forMode:NSDefaultRunLoopMode];
複製代碼
這兩個方法是等價的,區別是第一個方法默認建立了一個NSTimer並自動添加到了當前線程的Runloop中去,第二個須要咱們手動添加。若是當前線程是主線程的話,某些UI事件,好比UIScrollView的拖拽操做,會將Runloop切換成UITrackingRunLoopMode,這時候,默認的NSDefaultRunLoopMode模式中註冊的事件是不會被執行的。因此爲了設置一個不會被UI干擾的Timer,咱們須要手動將timer的當前RunloopMode設置爲NSRunLoopCommonModes,這個模式等效於NSDefaultRunLoopMode和UITrackingRunLoopMode的結合。html
上面的使用方法是沒問題的,可是你們在使用過程當中必定遇到過因使用了NSTimer,致使所在的UIViewController內存泄漏的問題,這種緣由是怎麼出現的呢?
其中許多人都認爲是UIViewController和NSTimer循環引用的問題,彼此強引用,致使了彼此沒法釋放,那麼問題真的是這樣嗎?
驗證以下:bash
NSTimer *timer = [NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(timerFired) userInfo:nil repeats:YES];
[[NSRunLoop mainRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
//結果:將NSTimer設置成局部變量,你會發現二者仍釋放不了。複製代碼
__weak typeof(self) weakSelf = self;
NSTimer *timer = [NSTimer timerWithTimeInterval:1.0 target:weakSelf selector:@selector(timerFired) userInfo:nil repeats:YES];
[[NSRunLoop mainRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
//結果:二者仍然沒法釋放。複製代碼
若是咱們將target強制釋放,強制破壞循環引用呢?app
TimerAction *Test = [TimerAction new];
NSTimer *timer = [NSTimer timerWithTimeInterval:1.0 target:Test selector:@selector(test) userInfo:nil repeats:YES];
[[NSRunLoop mainRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
CFRelease((__bridge CFTypeRef)(Test));
//結果:Test順利釋放,但Timer仍在運行。而且在Timer觸發事件時崩潰複製代碼
在timer創建後面斷點,查看運行的時候內存圖oop
結果:其實只有timer單向的指向target,target並未指向timer,是由於timer運行的時候釋放不了,致使被強引用的target也沒法釋放。並不是循環引用致使不釋放。ui
通常呢解決NSTimer的內存泄漏問題,一般有兩種方法,第一種是找對合適的時機釋放NSTimer,一般人們會想到兩個調用時機。
-(void)dealloc
{
[self.timer invalidate];
}
//NSTimer,一般人們會想到兩個調用時機。
複製代碼
-(void)viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
[self.timer invalidate];
}
//這種狀況是能夠解決循環引用的問題,內存能夠釋放,可是又會引來新的問題,當導航控制器push到下一個頁面時,當前VC並無被釋放,這時候咱們可能並不想銷燬NSTimer,咱們一般但願VC該銷燬的時候,同時銷燬NSTimer,因此調用invalidate方法的時機很難找複製代碼
那麼就是第二種了,想辦法破除強引用,讓NSTimer和VC同生共死,這種方法呢也有兩種方式
1.使用block的方式:atom
#import <Foundation/Foundation.h>
typedef void(^JSTimerBlcok)(NSTimer *timer);
@interface NSTimer (Category)
+ (NSTimer *)js_scheduledTimerWithTimeInterval:(NSTimeInterval)timeInterval executeBlock:(JSTimerBlcok)block repeats:(BOOL)repeats;
@end
#import "NSTimer+Category.h"
@implementation NSTimer (Category)
+(NSTimer *)js_scheduledTimerWithTimeInterval:(NSTimeInterval)timeInterval executeBlock:(JSTimerBlcok)block repeats:(BOOL)repeats
{
NSTimer *timer = [self scheduledTimerWithTimeInterval:timeInterval target:self selector:@selector(js_executeTimer:) userInfo:[block copy] repeats:repeats];
return timer;
}
+(void)js_executeTimer:(NSTimer *)timer
{
JSTimerBlcok block = timer.userInfo;
if (block) {
block(timer);
}
}
@end
使用案例: - (void)viewDidLoad { [super viewDidLoad]; __weak typeof(self) weakSelf = self; self.timer = [NSTimer js_scheduledTimerWithTimeInterval:1.0 executeBlock:^(NSTimer *timer){ __strong typeof(weakSelf) strongSelf = weakSelf; [strongSelf timerFired:timer]; } repeats:YES]; }複製代碼
2.使用NSProxy來初始化一個子類,這裏咱們直接用YYWeakProcyspa
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface YYWeakProxy : NSProxy
@property (nullable, nonatomic, weak, readonly) id target;
- (instancetype)initWithTarget:(id)target;
+ (instancetype)proxyWithTarget:(id)target;
@end
NS_ASSUME_NONNULL_END
#import "YYWeakProxy.h"
@implementation YYWeakProxy
- (instancetype)initWithTarget:(id)target {
_target = target;
return self;
}
+ (instancetype)proxyWithTarget:(id)target {
return [[YYWeakProxy alloc] initWithTarget:target];
}
- (id)forwardingTargetForSelector:(SEL)selector {
return _target;
}
- (void)forwardInvocation:(NSInvocation *)invocation {
void *null = NULL;
[invocation setReturnValue:&null];
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)selector {
return [NSObject instanceMethodSignatureForSelector:@selector(init)];
}
- (BOOL)respondsToSelector:(SEL)aSelector {
return [_target respondsToSelector:aSelector];
}
- (BOOL)isEqual:(id)object {
return [_target isEqual:object];
}
- (NSUInteger)hash {
return [_target hash];
}
- (Class)superclass {
return [_target superclass];
}
- (Class)class {
return [_target class];
}
- (BOOL)isKindOfClass:(Class)aClass {
return [_target isKindOfClass:aClass];
}
- (BOOL)isMemberOfClass:(Class)aClass {
return [_target isMemberOfClass:aClass];
}
- (BOOL)conformsToProtocol:(Protocol *)aProtocol {
return [_target conformsToProtocol:aProtocol];
}
- (BOOL)isProxy {
return YES;
}
- (NSString *)description {
return [_target description];
}
- (NSString *)debugDescription {
return [_target debugDescription];
}
@end
使用案例:
- (void)initTimer {
YYWeakProxy *proxy = [YYWeakProxy proxyWithTarget:self];
_timer = [NSTimer timerWithTimeInterval:0.1 target:proxy selector:@selector(tick:) userInfo:nil repeats:YES];
}
//至於具體的原理,讓NSTimer定時中的方法由YYWeakProxy轉發給VC執行.可是NStimer持有的卻不是VC.這樣就不會循環引用.複製代碼
你們應該知道,NSTimer的精確度通常能達到1ms,也就是小於1毫秒時,偏差會很大,那麼如何建立一個偏差很小,甚至沒有偏差的定時器呢
納秒級精度的Timer.net
#include <mach mach.h="">
#include <mach mach_time.h="">
static const uint64_t NANOS_PER_USEC = 1000ULL;
static const uint64_t NANOS_PER_MILLISEC = 1000ULL * NANOS_PER_USEC;
static const uint64_t NANOS_PER_SEC = 1000ULL * NANOS_PER_MILLISEC;
static mach_timebase_info_data_t timebase_info;
static uint64_t nanos_to_abs(uint64_t nanos) {
return nanos * timebase_info.denom / timebase_info.numer;
}
void waitSeconds(int seconds) {
mach_timebase_info(&timebase_info);
uint64_t time_to_wait = nanos_to_abs(seconds * NANOS_PER_SEC);
uint64_t now = mach_absolute_time();
mach_wait_until(now + time_to_wait);
}</mach></mach>
//理論上這是iPhone上最精準的定時器,能夠達到納秒級別的精度複製代碼
CADisplayLink線程
CADisplayLink * displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(logInfo)];
[displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
//CADisplayLink是一個頻率能達到屏幕刷新率的定時器類。iPhone屏幕刷新頻率爲60幀/秒,也就是說最小間隔能夠達到1/60s。複製代碼
GCD定時器debug
NSTimeInterval interval = 1.0;
_timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0));
dispatch_source_set_timer(_timer, dispatch_walltime(NULL, 0), interval * NSEC_PER_SEC, 0);
dispatch_source_set_event_handler(_timer, ^{
NSLog(@"GCD timer test");
});
dispatch_resume(_timer);
//RunLoop是dispatch_source_t實現的timer,因此理論上來講,GCD定時器的精度比NSTimer只高不低。複製代碼
參考資料: