IOS 如何优雅且实用地阻塞(后续)程序

  • 前言
  • NSTimer(推荐!)
  • NSThread
  • GCD
  • 结语


前言

有时需要延迟执行一些程序(如:跳转),首先想到的肯定是直接阻塞主进程(线程),使用 sleep:(NSTimeInterval)ti 即可。
但这种方式会使主线程的动画(animation)也被阻塞,因此并不适用于所有情况!

下面就介绍几种可以延迟执行程序,并且不会阻塞主进程的解决方案。
但由于 IOS 必须要在主线程操作 UI,所以涉及 UI 操作(包括跳转)不应该使用线程方法!

实验环境:XCode 10.1
语言:Objective-C

NSTimer(推荐!)

基于 NSTimer 设置一定时间间隔后,再执行函数,来实现“阻塞(后续)程序”的效果:

[NSTimer scheduledTimerWithTimeInterval:2 target:self 
		 selector:@selector(stopLoading) userInfo:nil repeats:NO];
  • 您可以使用上面这条语句在程序中间创建一个定时器。
  • 语句的含义为在 2s 之后,调用 @selector 中的函数。
  • 也就是说您需要想办法将想要延迟执行的程序,放在一个函数中,然后依赖定时器在一段时间后再调用函数,执行需要延迟执行的程序。
  • 注意:上面的语句会返回一个 NSTimer 对象,在这里我们不需要用到这个对象,因此代码中没有对返回值进行“接收”。

由于 NSTimer 没有阻塞主进程,只是设置了一个定时器,在一定时间间隔后通知主线程去调用一个预设的函数(@selector),因此不会对主进程的程序执行造成影响。

NSThread

使用 NSTimer 是一个非常取巧的办法,我们其实也可以自己创建新线程来实现上面的效果。

NSThread 是 Apple 提供的针对 PThread 封装的 OC 对象。只暴露了 PThread(底层 API,就像 axios 之于 fetch) 的部分能力,简单易用!

NSThread 提供了一个 + (void)sleepForTimeInterval:(NSTimeInterval)ti; 静态方法,可以对当前线程进行阻塞:

// 创建并开启子线程
NSThread *thread = [[NSThread alloc] initWithTarget:self 
									 selector:@selector(jump) 
									 object:nil];
    
[thread start];
- (void)jump{
    [NSThread sleepForTimeInterval:2];
	// sleep(2);
    // 需要延迟执行的代码
    ...
}

在子线程中使用 sleepForTimeInterval 静态方法,或者 sleep 对其进行阻塞,然后执行需要延迟执行的程序都可以!

但是需要注意,因为 UIKit 不是线程安全的(也就是说允许出现多个线程同时操作 UI 的情况),所以如果涉及 UI 的操作,就不应该使用线程方法,而应该使用 NSTimer 进行延迟执行。否则 XCode 会报错(但程序不一定会终止)。

GCD

Grand Central Dispatch(GCD) 是 Apple 开发的一个多核编程解决方法,是在线程池模式的基础上执行的并发任务。

我们可以使用 dispatch_queue_create("serial-queue", NULL) 创建一个串行队列,然后使用 dispatch_after 添加异步任务,并指定启动时间,实现异步、延迟执行程序。
GCD 给队列添加任务是完全基于 Block 的(无法使用函数指针)。下面是一个小小的示例:

dispatch_queue_t queue = dispatch_queue_create("serial-queue", NULL);
dispatch_after(
	dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), 
	queue, 
	^{	// 传入一个 block
		// 需要延迟执行的代码
		...
	 }
);

GCD 同样是基于线程的方法,只是使用了一个封装的线程池,涉及修改 UI 的操作时,也会报错,并且 GCD 会自动阻止相关代码的执行。

结语

本文提供的三个延迟执行程序的代码均经过了测试,但是只推荐大家使用 NSTimer。


如有差错,敬请指正。