iOS 多线程基础之 NSThread
前言
通常在 iOS 中,有三种比较常见的方式实现多线程,分别是 NSThread,GCD 和 NSOperation。本文主要介绍它们当中使用频率较低的 NSThread。
文章目录
- 前言
- NSThread
- 1. 线程创建
- 方法一:常规方法创建新线程
- 方法二:detachNewThread 系列类方法
- 方法三:NSObject + NSThreadPerformAdditions 分类中提供的方法
- 2. 线程优先级
- 优先级实验
- 新的优先级属性
- 3. 线程间通信
- 线程间通信实例
- 4. 常用属性和方法
- 改变线程状态的方法
- 判断线程状态的方法
- 主线程判断的方法
- 执行线程任务的方法
- NSThread 常用类方法
- 线程休眠相关方法
- 参考资料
NSThread
NSThread 是 Foundation 框架中封装的线程类,其对象就表示程序的一个线程。
1. 线程创建
我们有下面三种方法可以创建新线程去执行任务。
方法一:常规方法创建新线程
这种方法比较常规,通过对象创建和初始化方法进行新线程的创建。虽然写代码的步骤看起来比较复杂,但是可以获取到线程的对象,可以对线程进行名称、优先级之类的个性化修改。
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(threadRun) object:nil];NSThread *thread = [[NSThread alloc] initWithBlock:^{NSLog(@"Run %@", [NSThread currentThread]); }];// 需要手动调用 start 方法 [thread start];threadRun 方法表示线程将要执行的任务
- (void)threadRun {NSLog(@"Run %@", [NSThread currentThread]); }方法二:detachNewThread 系列类方法
我们可以通过 detachNewThread 系列的类方法创建新线程来执行任务,代码会比较简洁。但是这些方法都是没有返回值的,这就意味着我们无法获取到新创建的线程,进行无法修改线程设置。
[NSThread detachNewThreadSelector:@selector(threadRun) toTarget:self withObject:nil];[NSThread detachNewThreadWithBlock:^{NSLog(@"Run %@", [NSThread currentThread]); }];方法三:NSObject + NSThreadPerformAdditions 分类中提供的方法
NSObject + NSThreadPerformAdditions 分类中提供的 performSelectorInBackground: withObject: 方法也可以创建新线程执行任务,它的使用情况和方法二相似。
[self performSelectorInBackground:@selector(threadRun) withObject:nil];2. 线程优先级
线程是具有优先级的,线程的优先级代表 CPU 调度的优先级,优先级高的线程更容易得到 CPU 资源执行任务。对于 NSThread 对象,我们可以通过 threadPriority 属性来修改它的优先级,优先级的取值范围为 [0.0,1.0][0.0, 1.0][0.0,1.0],默认优先级为 0.5。
优先级实验
我们创建三个线程,优先级分别为 1.0 / 0.1 / 默认,让它们执行同一个任务,根据控制台输出结果,查看各优先级线程的任务执行情况。
// 设置线程 A 的优先级为 1.0 NSThread *threadA = [[NSThread alloc] initWithTarget:self selector:@selector(threadRun) object:nil]; threadA.name = @"threadA"; threadA.threadPriority = 1.0; [threadA start];// 设置线程 B 的优先级为 0.1 NSThread *threadB = [[NSThread alloc] initWithTarget:self selector:@selector(threadRun) object:nil]; threadB.name = @"threadB"; threadB.threadPriority = 0.1; [threadB start];// 让线程 C 保持默认优先级(0.5) NSThread *threadC = [[NSThread alloc] initWithTarget:self selector:@selector(threadRun) object:nil]; threadC.name = @"threadC"; [threadC start];// 线程要执行的任务 - (void)threadRun {for (int i = 0; i < 100; i++) {NSLog(@"Run %@ - %d", [NSThread currentThread].name, i);} }根据上面例子的执行结果可以看出来,优先级高的线程能更快地完成它需要执行的任务。
新的优先级属性
从官方的注释中,我们发现 threadPriority 属性在将来会被弃用,用 qualityOfService 作为新的线程优先级。
// 新版的线程优先级 @property NSQualityOfService qualityOfService;typedef NS_ENUM(NSInteger, NSQualityOfService) {// 和图形处理相关的任务,比如滚动和动画NSQualityOfServiceUserInteractive = 0x21,// 用户请求的任务,但是不需要精确到毫秒级。例如,用户请求打开电子邮件App来查看邮件NSQualityOfServiceUserInitiated = 0x19,// 周期性的用户请求任务。比如,电子邮件App可能被设置成每5分钟自动检测新邮件NSQualityOfServiceUtility = 0x11,// 后台任务,对这些任务用户可能并不会察觉NSQualityOfServiceBackground = 0x09,// 默认的优先级,优先级在 Utility 之前NSQualityOfServiceDefault = -1 } API_AVAILABLE(macos(10.10), ios(8.0), watchos(2.0), tvos(9.0));3. 线程间通信
通常线程间通信主要有下面两种情形:
- 一个线程的数据传递给另一个线程
- 一个线程的任务执行完后,转到另一个线程继续执行任务
与 NSThread 有关的线程间通信实现的方法在 NSObject + NSThreadPerformAdditions 分类中,常用的是下面两个方法。
// 到主线程执行指定任务 - (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait;// 到指定线程执行指定任务 - (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait线程间通信实例
这里用一个经典实例,来对线程间通信进行讲解。我们要在网上下载图片,然后展示在手机屏幕上显示。为了不阻塞主线程,我们需要异步进行图片的下载,又因为 iOS 不能在子线程更新 UI,我们需要在图片下载完成之后回到主线程将下载好的图片更新到 UIImageView 上。
@interface ViewController ()// 用于展示图片 @property (nonatomic, strong) UIImageView *imageView;@end@implementation ViewController- (void)viewDidLoad {[super viewDidLoad];[self.view addSubview:self.imageView];// 设置约束[self.imageView mas_makeConstraints:^(MASConstraintMaker *make) {make.center.equalTo(self.view);make.size.mas_equalTo(CGSizeMake(216, 384));}];// 创建线程NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(downloadImage) object:nil];// 启动线程[thread start]; }#pragma mark - Actions// 后台线程处理方法 - (void)downloadImage {// 耗时操作,下载网络图片NSURL *url = [NSURL URLWithString:@"https://pics3.baidu.com/feed/b151f8198618367afc010b4f3e2a1dd2b21ce573.jpeg?token=a80568b00de1ae2814069f2f70241a96"];NSData *imgData = [NSData dataWithContentsOfURL:url];// 执行耗时操作完成,返回主线程处理[self performSelectorOnMainThread:@selector(updateUIWithData:) withObject:imgData waitUntilDone:YES]; }// 需要在主线程执行的操作 - (void)updateUIWithData:(NSData *)data {// 刷新 UI 的方法UIImage *image = [UIImage imageWithData:data];self.imageView.image = image; }#pragma mark - Getter- (UIImageView *)imageView {if (!_imageView) {_imageView = [[UIImageView alloc] init];_imageView.backgroundColor = [UIColor grayColor];}return _imageView; }@end4. 常用属性和方法
因为前面上面提到的 NSThread 使用方式都有更加便捷的 GCD 函数代替,所以相比之下,下面这些方法反而会更加常用。
改变线程状态的方法
// 启动线程执行任务,创建 -> 就绪 [thread start]; // 标记线程为 cancelled 状态 [thread cancel]; // 退出线程执行, -> 终止 [NSThread exit];判断线程状态的方法
// 判断任务是否被取消 BOOL isCancelled = [thread isCancelled]; // 判断任务是否正在执行 BOOL isExecuting = [thread isExecuting]; // 判断任务是否执行完成 BOOL isFinished = [thread isFinished];主线程判断的方法
// 判断 thread 是否为主线程 BOOL isMainThread = [thread isMainThread];执行线程任务的方法
// 在当前线程中执行 thread 对象的任务 [thread main];NSThread 常用类方法
// 判断是否处在多线程环境 [NSThread isMultiThreaded];// 获取当前线程 [NSThread currentThread]// 获取主线程 [NSThread mainThread];线程休眠相关方法
// 让线程休眠到 data 所在时间 [NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:1.0]]; // 让线程休眠 ti 秒 [NSThread sleepForTimeInterval:1.0];参考资料
- NSThread | Apple Developer Documentation
总结
以上是生活随笔为你收集整理的iOS 多线程基础之 NSThread的全部内容,希望文章能够帮你解决所遇到的问题。
- 上一篇: iOS - 数据持久化之 FMDB 的使
- 下一篇: 有 OC 经验的程序员快速学习 Swif