系统提供了三种创建线程的方法
- (instancetype)init;
// 通过指定对象和方法选择器的方式,argument是传递的参数
- (instancetype)initWithTarget:(id)target selector:(SEL)selector object:(nullable id)argument;
// 注意,这种方式是iOS10之后才增加的,将任务放在了block种执行
- (instancetype)initWithBlock:(void (^)(void))block;
@property (nullable, copy) NSString *name; // 线程名称
@property NSUInteger stackSize; // 栈的大小,4k的倍数,在线程start之前设置
@property double threadPriority; // 线程的优先级,【0-1】,值越大,优先级别越高,该属性不久的将来会废弃,可使用下面的属性
@property NSQualityOfService qualityOfService; // 线程优先级,iOS8.0之后新增,有一下几种
NSQualityOfService优先级分类
NSQualityOfServiceUserInteractive // 与用户交互的任务,这些任务通常跟UI级别的刷新相关,比如动画,这些任务需要在一瞬间完成
NSQualityOfServiceUserInitiated // 由用户发起的并且需要立即得到结果的任务,比如滑动scroll view时去加载数据用于后续cell的显示,这些任务通常跟后续的用户交互相关,在几秒或者更短的时间内完成
NSQualityOfServiceUtility // 一些可能需要花点时间的任务,这些任务不需要马上返回结果,比如下载的任务,这些任务可能花费几秒或者几分钟的时间
NSQualityOfServiceBackground // 这些任务对用户不可见,比如后台进行备份的操作,这些任务可能需要较长的时间,几分钟甚至几个小时
NSQualityOfServiceDefault // 优先级介于user-initiated 和 utility,当没有 QoS信息时默认使用,开发者不应该使用这个值来设置自己的任务
- (void)start; // 启动线程
- (void)cancel; // 取消线程
@property (readonly, getter=isExecuting) BOOL executing; // 线程是否正在执行
@property (readonly, getter=isFinished) BOOL finished; // 线程是否完成
@property (readonly, getter=isCancelled) BOOL cancelled; // 线程是否已经取消
// 创建线程
NSThread *thread = [[NSThread alloc] initWithBlock:^{
NSLog(@"执行线程任务。。。");
}];
// 设置一些属性
thread.name = @"线程1";
// 开启线程
[thread start];
NSLog(@"线程信息:%@",thread);
// 取消线程
[thread cancel];
NSLog(@"线程是否被取消:%i",thread.isCancelled);
如果需要在线程任务中传递一些参数,我们需要使用另外一种创建方式。
// 创建线程
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(run:) object:@{@"name":@"LOITA0164",@"age":@"25"}];
// 开启线程
[thread start];
-(void)run:(id)object{
NSLog(@"传递过来的参数:%@",object);
}
小结
当我们创建NSThread实例后,我们为其设置一些属性,例如线程名称、线程优先级、栈大小等信息,与其相对的,我们需要手动管理线程的开始和取消等操作。
NSThread类不仅为我们提供了创建实例的方法,另外为我们提供了快捷使用线程的类方法,当你使用这类方法时,你的关注点不再是线程的创建和开启,而仅仅是想让线程去执行某些任务,另外,类的方法会自动开始执行线程任务。
注:使用类方法时,针对的都是当前的线程
同样的,有两种快捷使用方式,一种是通过block方式执行线程任务,另一种是通过对象和方法选择器实现线程任务。
// block方式执行任务
+ (void)detachNewThreadWithBlock:(void (^)(void))block API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0));
// 方法选择器执行任务
+ (void)detachNewThreadSelector:(SEL)selector toTarget:(id)target withObject:(nullable id)argument;
示例
[NSThread detachNewThreadWithBlock:^{
NSLog(@"执行新线程任务");
}];
[NSThread detachNewThreadSelector:@selector(run:) toTarget:self withObject:@[@"apple",@"pear"]];
-(void)run:(id)object{
NSLog(@"传递过来的参数:%@",object);
}
NSThread提供了一些类方法,管理线程的调度,获取当前的线程,线程的信息等
@property (class, readonly, strong) NSThread *mainThread;//获取到主线程
@property (class, readonly, strong) NSThread *currentThread;//获取当前线程
@property (class, readonly) BOOL isMainThread;//当前是否是主线程
+ (BOOL)isMultiThreaded;//是否是多线程
+ (void)sleepUntilDate:(NSDate *)date;//当前线程休眠到某时刻
+ (void)sleepForTimeInterval:(NSTimeInterval)ti;//当前线程休眠时长
+ (void)exit;//退出当前线程
+ (double)threadPriority;//当前线程的优先级
+ (BOOL)setThreadPriority:(double)p;//设置当前线程的优先级
示例
我们修改一下任务run的实现
-(void)run:(id)object{
// 获取主线程
NSThread *mainThread = [NSThread mainThread];
mainThread.name = @"主线程";
NSLog(@"主线程:%@",mainThread);
// 获取当前的线程,可以通过这个线程实例使用线程方法
NSThread *currentThread = [NSThread currentThread];
currentThread.name = @"子线程";
NSLog(@"当前线程:%@",currentThread);
// 线程的优先级
[NSThread setThreadPriority:0.8];
NSLog(@"%f-%f",[NSThread threadPriority],currentThread.threadPriority);
// 线程信息
NSLog(@"多线程?:%i",[NSThread isMultiThreaded]);
// 是否是主线程
NSLog(@"当前线程是否是主线程:%i-%i",[NSThread isMainThread],[currentThread isMainThread]);
// 线程休眠(阻塞)
// 休眠时长
[NSThread sleepForTimeInterval:1.0];
// 休眠到某个时间点
[NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:1.0]];
// 终止线程,一旦执行exit,线程之后的任务不再被执行
[NSThread exit];
}
小结
在某一线程任务中(包括主线程)使用NSThread的类方法时,都是针对某一线程的,如设置优先级、阻塞、终止等
截止到现在,我们已经知晓了如何创建线程,管理线程,传递参数等等,但是读者仔细观察一下NSThread的方法,其中并没有为线程赋予任务的方法,这意味着我们通过[[NSThread alloc] init]
、[NSThread new]
的线程无法指定其任务,那么如果遇到一个需求:耗时的操作开辟子线程去完成,完成后进行刷新UI,这样的创建线程能否完成呢?我们知道UI需要在主线程中去完成的,那么为了完成上述需求,我们势必要在子线程中取到主线程,让其完成UI的更新,但是,正如之前提到的,NSThread并没有为线程赋予任务的方法,并且当前线程是只读属性,无法更换为主线程,因此,这种需求显然是无法实现的。(笔者试过取消当前线程,调用更新UI;还是创建block作为参数,回调结果更新UI,最终发现当前线程都是子线程,这说明消息传递默认都是在当前线程上完成的)
那么,NSThread无法完成了上述的需求了吗?答案是否定的,在NSThread的最后,apple扩展了NSObject对象,这些方法,是从实例对象角度,选择线程执行任务的,之前的NSThread都是从线程角度完成任务。
// 在主线程完成任务
// waitUntilDone:表示是否阻塞当前线程等待新任务结束(结束后会继续执行后面任务)
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait modes:(nullable NSArray<NSString *> *)array;
// 相当于第一种方法使用kCFRunLoopCommonModes模式
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait;
// 选择某一线程完成任务
// waitUntilDone:表示是否阻塞当前线程等待新任务结束(结束后会继续执行后面任务)
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait modes:(nullable NSArray<NSString *> *)array;
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait NS_AVAILABLE(10_5, 2_0);//相当于第一种方法使用kCFRunLoopCommonModes模式
// 在后台完成任务
- (void)performSelectorInBackground:(SEL)aSelector withObject:(nullable id)arg;
示例
我们来完成上述需求
@interface ViewController (){
NSInteger _count; // 定义一个计数器
}
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// 开启一个线程
[NSThread detachNewThreadSelector:@selector(run) toTarget:self withObject:nil];
}
-(void)run{
NSLog(@"%s,当前线程:%@",__func__,[NSThread currentThread]);
_count = 1;
NSLog(@"UI更新前的count:%ld",_count);
// 在主线程上更新UI,这里只做计算器加1
// 注:这里的waitUntilDone是YES,表示等待新任务结束后再继续执行
[self performSelectorOnMainThread:@selector(updateUI:) withObject:@(_count) waitUntilDone:YES];
NSLog(@"UI更新后的count:%ld",_count);
}
-(void)updateUI:(NSNumber*)count{
NSLog(@"%s,当前线程:%@",__func__,[NSThread currentThread]);
_count++;
}
结果
我们发现,updateUI中的线程是主线程,而不是子线程,另外我们发现,count变成了2,说明run中的任务是顺序执行的。
我们发现,count依旧输出了两次,但是数量都是1,这说明run中的任务并没有顺序执行,因此waitUntilDone为YES还是NO需要根据需求而定。
另外需要注意的是,此时updateUI的主线程任务是加到主线程任务最后才被执行的,先来先服务的原则。
为了对比NSThreadPerformAdditions
扩展调用方法和普通的访问的不同,我们将
[self performSelectorOnMainThread:@selector(updateUI:) withObject:@(_count) waitUntilDone:NO];
改为
[self updateUI:@(_count)];
结果:
我们发现结果似乎和第一次一样,但是,需要注意的是,
,这里,这说明线程依旧是原来的子线程(number为1时,才是主线程,因为number是递增的,主线程最先被创建出来,其值为1)
那么,这种调用方式似乎等同于下面的方法
[self performSelector:@selector(updateUI:) onThread:[NSThread currentThread] withObject:@(_count) waitUntilDone:YES];
结果:
结果一致,[object method]
这种方式都是在当前线程上执行的。
类比拓展:延迟执行
[self performSelector:@selector(updateUI:) withObject:@(_count) afterDelay:1.0];
等同于
[NSThread sleepForTimeInterval:1.0];
[self performSelector:@selector(updateUI:) onThread:[NSThread currentThread] withObject:@(_count) waitUntilDone:YES];
1、我们可以创建子线程完成一些其他任务
2、NSThread提供了一些类方法,让我们可以便捷的创建使用子线程
3、NSThread的对象都是在创建时指定了线程任务,并未提供给线程赋予执行新任务的方式(因此即使你在子线程中拿到了主线程对象,你也无法执行主线程完成一些任务)
4、NSObject扩展了一些方法,用来指定在某些线程上完成任务
5、子线程执行耗时操作,主线程更新UI的任务需求
6、[object method]
这种调用方式是以当前线程来执行的