NSTimer如何使用
方法1: Creates a timer and schedules it on the current run loop in the default mode.
1 2 3 4 5 + (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval )ti target:(id )aTarget selector:(SEL)aSelector userInfo:(id )userInfo repeats:(BOOL )yesOrNo;
方法2: Initializes a timer object with the specified object and selector.
You must add the new timer to a run loop, using addTimer:forMode: . Then, after ti seconds have elapsed, the timer fires, sending the message aSelector to target. (If the timer is configured to repeat, there is no need to subsequently re-add the timer to the run loop.)
1 2 3 4 5 6 7 + (NSTimer *)timerWithTimeInterval:(NSTimeInterval )ti target:(id )aTarget selector:(SEL)aSelector userInfo:(id )userInfo repeats:(BOOL )yesOrNo; [[NSRunLoop mainRunLoop] addTimer:self .timer forMode:NSDefaultRunLoopMode ];
这两个方法是等价的,区别是第一个方法默认创建了一个NSTimer并自动添加到了当前线程的Runloop中去,第二个需要我们手动添加。如果当前线程是主线程的话,某些UI事件,比如UIScrollView的拖拽操作,会将Runloop切换成UITrackingRunLoopMode,这时候,默认的NSDefaultRunLoopMode模式中注册的事件是不会被执行的。所以为了设置一个不会被UI干扰的Timer,我们需要手动将timer的当前RunloopMode设置为NSRunLoopCommonModes,这个模式等效于NSDefaultRunLoopMode和UITrackingRunLoopMode的结合。
由于NSTimer会强引用Target,RunLoop会强引用NSTimer,若ViewController再强引用NSTimer,NSTimer找不到合适的时机释放的时候,很容易造成内存泄漏,这个问题如何解决?
如何解决NSTimer找不到释放时机的问题? 方案1,使用官方API带Block的方式来创建NSTimer 具体的创建方法如下,但是仅支持iOS 10以上的系统。
1 + (NSTimer *)timerWithTimeInterval:(NSTimeInterval )interval repeats:(BOOL )repeats block:(void (^)(NSTimer *timer))block API_AVAILABLE(macosx(10.12 ), ios(10.0 ), watchos(3.0 ), tvos(10.0 ));
在ViewController中的使用方法:
1 2 3 4 5 6 __weak typeof (self )weakSelf = self ; self .timer = [NSTimer timerWithTimeInterval:1.0 repeats:YES block:^(NSTimer * _Nonnull timer) { NSLog (@"第%ld次执行" , (long )weakSelf.count); weakSelf.count ++; }]; [[NSRunLoop currentRunLoop] addTimer:self .timer forMode:NSRunLoopCommonModes ];
方案2,使用Block的方式来创建NSTimer 具体实现方式是,需要创建一个NSTimer的分类,让外部传入一个Block,NSTimer每次到时间就执行传入进来的Block。新建一个分类: NSTimer+BlockSupport.h
1 2 3 4 5 6 7 8 9 10 11 12 13 #import <Foundation/Foundation.h> NS_ASSUME_NONNULL_BEGIN @interface NSTimer (BlockSupport )+ (NSTimer *)cd_timerWithTimeInterval:(NSTimeInterval )interval repeats:(BOOL )repeats block:(void (^)(void ))block; @end NS_ASSUME_NONNULL_END
NSTimer+BlockSupport.m
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 #import "NSTimer+BlockSupport.h" @implementation NSTimer (BlockSupport )+ (NSTimer *)cd_timerWithTimeInterval:(NSTimeInterval )interval repeats:(BOOL )repeats block:(void (^)(void ))block { return [self scheduledTimerWithTimeInterval:interval target:self selector:@selector (cd_blockInvoke:) userInfo:[block copy ] repeats:repeats]; } + (void )cd_blockInvoke:(NSTimer *)timer { void (^block)(void ) = timer.userInfo; if (block) { block(); } } @end
在ViewController中使用方法:
1 2 3 4 5 6 __weak typeof (self )weakSelf = self ; self .timer = [NSTimer cd_timerWithTimeInterval:1.0 repeats:YES block:^{ NSLog (@"第%ld次执行" , (long )weakSelf.count); weakSelf.count ++; }]; [[NSRunLoop currentRunLoop] addTimer:self .timer forMode:NSRunLoopCommonModes ];
方案3,自定义NSTimer的Target 封装自定义NSTimer的Target和NSTimer的工具类,让NSTimer在合适的时机释放。 CDTimerTarget.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 #import <Foundation/Foundation.h> NS_ASSUME_NONNULL_BEGIN @interface CDTimerTarget : NSObject @property (nonatomic ,weak ) id target;@property (nonatomic ,assign ) SEL selector;@end NS_ASSUME_NONNULL_END
CDTimerTarget.m
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 #import "CDTimerTarget.h" @implementation CDTimerTarget - (void )fire:(NSTimer *)timer { if (self .target) { #pragma clang diagnostic push #pragma clang diagnostic ignored "-Warc-performSelector-leaks" [self .target performSelector:self .selector withObject:timer.userInfo]; #pragma clang diagnostic pop }else { [timer invalidate]; } } @end
CDTimer.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 #import <Foundation/Foundation.h> NS_ASSUME_NONNULL_BEGIN @interface CDTimer : NSObject + (NSTimer *)scheduleTimerWithTimerInterval:(NSTimeInterval )interval target:(id )aTarget selector:(SEL)aSelector userInfo:(id )userInfo repeats:(BOOL )repeats; @end NS_ASSUME_NONNULL_END
CDTimer.m
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 #import "CDTimer.h" #import "CDTimerTarget.h" @implementation CDTimer + (NSTimer *)scheduleTimerWithTimerInterval:(NSTimeInterval )interval target:(id )aTarget selector:(SEL)aSelector userInfo:(id )userInfo repeats:(BOOL )repeats { CDTimerTarget *timerTarget = [[CDTimerTarget alloc]init]; timerTarget.target = aTarget; timerTarget.selector = aSelector; NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:interval target:timerTarget selector:@selector (fire:) userInfo:userInfo repeats:repeats]; return timer; } @end
在ViewController中的用法:
1 self .timer = [CDTimer scheduleTimerWithTimerInterval:1.0 target:self selector:@selector (repeatAction) userInfo:nil repeats:YES ];
方案4,自定义NSProxy子类,作为NSTimer的Target
TargetProxy.h
1 2 3 4 5 6 7 8 9 10 11 #import <Foundation/Foundation.h> NS_ASSUME_NONNULL_BEGIN @interface TargetProxy : NSProxy + (instancetype )proxyWithTarget:(id )target; @end NS_ASSUME_NONNULL_END
TargetProxy.m
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 #import "TargetProxy.h" @interface TargetProxy ()@property (nonatomic , weak )id target;@end @implementation TargetProxy + (instancetype )proxyWithTarget:(id )target { TargetProxy *proxy = [[self class ] alloc]; proxy.target = target; return proxy; } - (NSMethodSignature *)methodSignatureForSelector:(SEL)sel { return [self .target methodSignatureForSelector:sel]; } - (void )forwardInvocation:(NSInvocation *)invocation { if ([self .target respondsToSelector:invocation.selector]) { [invocation invokeWithTarget:self .target]; }else { [invocation doesNotRecognizeSelector:invocation.selector]; } } @end
在ViewController中的使用方法:
1 self .timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:[TargetProxy proxyWithTarget:self ] selector:@selector (repeatAction) userInfo:nil repeats:YES ];
参考链接 NSTimer
NSProxy解决NSTimer和CADisplayLink的循环引用
《Effective Objective-C 2.0 编写高质量iOS与OS X代码的52个有效方法》