欢迎访问 生活随笔!

生活随笔

当前位置: 首页 > 编程资源 > 编程问答 >内容正文

编程问答

iOS 多线程基础之 NSThread

发布时间:2025/3/20 编程问答 35 豆豆
生活随笔 收集整理的这篇文章主要介绍了 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; }@end

4. 常用属性和方法

因为前面上面提到的 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的全部内容,希望文章能够帮你解决所遇到的问题。

如果觉得生活随笔网站内容还不错,欢迎将生活随笔推荐给好友。