在程序当中经常需要延时执行某些操作,而常用的延时方法有四种。
performSelector方法
声明
- (void)performSelector:(SEL)aSelector withObject:(nullable id)anArgument afterDelay:(NSTimeInterval)delay ;
代码举例
[self performSelector:@selector(method:) withObject:nil afterDelay:2.0] ;
注意
异步执行,不会阻塞当前线程。
由于该方法是基于runloop的,因此必须在一个活跃的runloop中调用。主线程的runloop不会停止,因此在主线程中该方法可以正常调用;而子线程的runloop默认是关闭的,如果不手动将其激活,该方法在子线程的调用将是无效的。
-
在主线程中调用方法
- (void)viewDidLoad { [super viewDidLoad] ; [self performSelector:@selector(method) withObject:nil afterDelay:2.0] ; } - (void)method { NSLog(@"%@",[NSThread currentThread]) ; }
打印结果为<NSThread: 0x60800007c440>{number = 1, name = main}
,方法调用成功。
- 在子线程中调用方法(不手动激活)
- (void)viewDidLoad {
[super viewDidLoad] ;
dispatch_async(dispatch_queue_create("test", NULL), ^{
[self performSelector:@selector(method) withObject:nil afterDelay:2.0] ;
}) ;
}
- (void)method {
NSLog(@"%@",[NSThread currentThread]) ;
}
无打印结果,方法调用失败。
- 在子线程中调用方法(手动激活)
- (void)viewDidLoad {
[super viewDidLoad] ;
dispatch_async(dispatch_queue_create("test", NULL), ^{
[self performSelector:@selector(method) withObject:nil afterDelay:2.0] ;
[[NSRunLoop currentRunLoop] addPort:[NSPort port] forMode:NSDefaultRunLoopMode] ;
[[NSRunLoop currentRunLoop] run] ;
}) ;
}
- (void)method {
NSLog(@"%@",[NSThread currentThread]) ;
}
打印结果为<NSThread: 0x608000073e40>{number = 3, name = (null)}
,方法调用成功。
- 可以通过类方法
cancelPreviousPerformRequestsWithTarget:
和cancelPreviousPerformRequestsWithTarget:selector:object:
来取消执行,但是必须和其创建在同一个线程中。 - 可以通过方法
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait;
确保aSelector
方法会在主线程执行。- 若
wait
为YES,则等待当前线程执行完以后主线程才会执行aSelector
方法。 - 若
wait
为NO,则不等当前线程执行完就在主线程中执行aSelector
方法。
- 若
- 通过该方法延时执行
aSelector
方法时,最多只能传入一个参数并且无法获得方法的返回值。
NSTimer定时器
声明
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo ;
代码举例
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(method:) userInfo:nil repeats:NO] ;
注意
- 异步执行,不阻塞线程。
- 由于该方法是基于runloop的,因此必须在一个活跃的runloop中调用。主线程的runloop不会停止,因此在主线程中该方法可以正常调用;而子线程的runloop默认是关闭的,如果不手动将其激活,该方法在子线程的调用将是无效的。
- 在主线程中调用方法
- (void)viewDidLoad {
[super viewDidLoad] ;
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(method) userInfo:nil repeats:NO] ;
}
- (void)method {
NSLog(@"%@",[NSThread currentThread]) ;
}
打印结果为<NSThread: 0x600000261680>{number = 1, name = main}
,方法调用成功。
- 在子线程中调用方法(不手动激活)
- (void)viewDidLoad {
[super viewDidLoad] ;
dispatch_async(dispatch_queue_create("test", NULL), ^{
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(method) userInfo:nil repeats:NO] ;
}) ;
}
- (void)method {
NSLog(@"%@",[NSThread currentThread]) ;
}
无打印结果,方法调用失败。
- 在子线程中调用方法(手动激活)
- (void)viewDidLoad {
[super viewDidLoad] ;
dispatch_async(dispatch_queue_create("test", NULL), ^{
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(method) userInfo:nil repeats:NO] ;
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode] ;
[[NSRunLoop currentRunLoop] run] ;
}) ;
}
- (void)method {
NSLog(@"%@",[NSThread currentThread]) ;
}
打印结果为<NSThread: 0x6000002615c0>{number = 3, name = (null)}
,方法调用成功。
- 可以通过
-(void)invalidate:
方法取消执行,但是必须和其创建在同一个线程中。 - NSTimer定时器存在内存泄漏的风险。通过NSTimer定时器生成的timer会被NSRunLoop对象一直持有,直到调用invalidate方法。而timer又持有target对象,如果不调用
invalidate
方法,target对象将会一直无法被释放,从而造成内存泄漏。
NSThread的sleep方法
声明
+ (void)sleepForTimeInterval:(NSTimeInterval)ti ;
代码举例
[NSThread sleepForTimeInterval:2.0] ;
[self method] ;
注意
- 同步执行,会阻塞线程。
- 在主线程和子线程都可以执行。
GCD
声明
void dispatch_after(dispatch_time_t when, dispatch_queue_t queue, dispatch_block_t block) ;
代码举例
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[self method] ;
});
注意
- 异步执行,不会阻塞线程。
- 在主线程和子线程都可以执行。
- 一旦执行就无法撤销。
- 系统会帮助处理线程级的逻辑,并且调用的对象也不会被强行持有,这样就不会存在内存泄漏的问题。