前言
通常在 iOS 中,有三种比较常见的方式实现多线程,分别是 NSThread ,GCD 和 NSOperation 。本文主要介绍它们当中使用频率较低的 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]);
}];
[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 / 默认,让它们执行同一个任务,根据控制台输出结果,查看各优先级线程的任务执行情况。
NSThread *threadA = [[NSThread alloc] initWithTarget:self selector:@selector(threadRun) object:nil];
threadA.name = @"threadA";
threadA.threadPriority = 1.0;
[threadA start];
NSThread *threadB = [[NSThread alloc] initWithTarget:self selector:@selector(threadRun) object:nil];
threadB.name = @"threadB";
threadB.threadPriority = 0.1;
[threadB start];
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,
NSQualityOfServiceUserInitiated = 0x19,
NSQualityOfServiceUtility = 0x11,
NSQualityOfServiceBackground = 0x09,
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:
NSData *imgData = [NSData dataWithContentsOfURL:url];
[self performSelectorOnMainThread:@selector(updateUIWithData:) withObject:imgData waitUntilDone:YES];
}
- (void)updateUIWithData:(NSData *)data {
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];
[thread cancel];
[NSThread exit];
判断线程状态的方法
BOOL isCancelled = [thread isCancelled];
BOOL isExecuting = [thread isExecuting];
BOOL isFinished = [thread isFinished];
主线程判断的方法
BOOL isMainThread = [thread isMainThread];
执行线程任务的方法
[thread main];
NSThread 常用类方法
[NSThread isMultiThreaded];
[NSThread currentThread]
[NSThread mainThread];
线程休眠相关方法
[NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:1.0]];
[NSThread sleepForTimeInterval:1.0];
参考资料
|