NSThread是官方提供的一套面向对象的轻量级多线程开发技术。使用较为简单,不需要过多地操作线程的行为配置,但是仍然需要开发者自己处理线程的生命周期。相比于C语言中的pthread相关接口,NSThread易用性更强。
一、NSThread开启新线程的方式
1、构造器方式
NSThread中提供了如下两个类方法:
+ (void)detachNewThreadWithBlock:(void (^)(void))block)
+ (void)detachNewThreadSelector:(SEL)selector toTarget:(id)target withObject:(nullable id)argument
两个方法作用类似,都是自动开启新线程,执行任务。
2、初始化方式
通过手动调用初始化方法,可以获取到线程对象,从而更方便地对线程进行配置以及获取线程信息。示例如下:
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
NSLog(@"%@", [NSThread currentThread]);
NSThread *thread1 = [[NSThread alloc] initWithBlock:^{
NSLog(@"1_%@", [NSThread currentThread]);
}];
[thread1 start];
NSThread *thread2 = [[NSThread alloc] initWithTarget:self selector:@selector(test) object:nil];
[thread2 start];
}
- (void)test {
NSLog(@"2_%@", [NSThread currentThread]);
}
运行代码,控制台输出如下:
2021-12-23 14:12:11.726978+0800 MyProject[38641:550626] <_NSMainThread: 0x6000039c8080>{number = 1, name = main}
2021-12-23 14:12:11.727314+0800 MyProject[38641:550803] 1_<NSThread: 0x600003983500>{number = 7, name = (null)}
2021-12-23 14:12:11.727395+0800 MyProject[38641:550804] 2_<NSThread: 0x600003983340>{number = 8, name = (null)}
需要注意的是,初始化方式创建线程,需要调用start方法来启动线程任务。
3、自定义线程
通过继承NSThread可以创建自定义的线程,自定义线程通过内部main
函数设定要执行的任务。示例如下:
#import "MyThread.h"
@implementation MyThread
- (void)main {
NSLog(@"自定义线程:%@", [NSThread currentThread]);
}
@end
自定义线程的使用方法如下:
MyThread *thread = [[MyThread alloc] init];
[thread start];
4、performSelector
只要是NSObject的子类或实例都可以通过调用方法进入子线程和主线程,其实这些方法开辟的子线程,也是NSThread的一种体现方式。常用方法如下:
// 当前线程,延时1s执行
[self performSelector:@selector(text) withObject:nil afterDelay:1];
该方法也响应了Objective-C的动态性:延时到运行时才绑定方法。需要注意的是,带afterDelay
的延时函数,会在内部创建一个NSTimer,然后添加到当前线程的runloop中。如果当前线程没有开启Runloop,则方法会失效,例如:
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[self performSelector:@selector(test) withObject:nil afterDelay:0];
});
这里test方法是不会执行的,因为
- (void)performSelector:(SEL)aSelector withObject:(nullable id)anArgument afterDelay:(NSTimeInterval)delay
这个方法要创建提交任务到runloop上,而GCD底层创建的线程是默认不开启对应的runloop的,所以test不会执行。
如果将dispatch_get_global_queue
改为主队列,由于主队列所在的主线程是默认开启runloop的,则回去执行test。如果将dispatch_async
改为dispatch_sync
,同步在当前线程执行,如果当前线程是主线程,则test可以执行。
// 回到主线程
// waitUntilDone YES - 立刻执行 NO - 等待当前Runloop空闲后执行
[self performSelectorOnMainThread:@selector(test) withObject:nil waitUntilDone:YES];
// 开辟子线程
[self performSelectorInBackground:@selector(test) withObject:nil];
// 在指定的线程中执行任务
// 任务执行依赖Runloop,所以线程Runloop必须开启
[self performSelector:@selector(test) onThread:[NSThread currentThread] withObject:nil waitUntilDone:YES];
二、相关属性与方法
1、获取线程信息的类方法、属性
// 获取当前线程NSThread对象
@property (class, readonly, strong) NSThread *currentThread;
// 获取主线程NSThread对象
@property (class, readonly, strong) NSThread *mainThread;
// 当前应用程序是否支持多线程
+ (BOOL)isMultiThreaded;
// 当前是否为主线程
@property (class, readonly) BOOL isMainThread;
// 获取当前线程的优先级
+ (double)threadPriority;
// 当前线程执行代码的堆栈地址
@property (class, readonly, copy) NSArray<NSNumber *> *callStackReturnAddresses;
// 当前线程执行代码的堆栈信息
@property (class, readonly, copy) NSArray<NSString *> *callStackSymbols;
2、控制线程行为的类方法
// 使当前线程休眠到指定时间
+ (void)sleepUntilDate:(NSDate *)date;
// 使当前线程休眠一定时间
+ (void)sleepForTimeInterval:(NSTimeInterval)ti;
// 结束当前线程
+ (void)exit;
// 设置当前线程的优先级
+ (BOOL)setThreadPriority:(double)p;
3、其他常用实例属性、方法
// 线程名称
@property (nullable, copy) NSString *name;
// 堆栈大小
@property NSUInteger stackSize;
// 是否为主线程
@property (readonly) BOOL isMainThread;
// 是否正在执行
@property (readonly, getter=isExecuting) BOOL executing;
// 是否执行完成
@property (readonly, getter=isFinished) BOOL finished;
// 是否已经取消
@property (readonly, getter=isCancelled) BOOL cancelled;
// 取消线程(线程不会立即停止执行,需要开发者根据cancelled属性做逻辑处理)
- (void)cancel;
// 启动线程
- (void)start;
// 线程主体,自定义的NSThread子类通过重写该方法来指定要执行的任务
- (void)main;
三、相关通知
系统定义了几个线程相关的通知,我们可以通过监听来关注多线程的运行状态,具体名称以及发送时机如下:
// 将进入多线程运行模式
FOUNDATION_EXPORT NSNotificationName const NSWillBecomeMultiThreadedNotification;
// 已经进入多线程运行模式
FOUNDATION_EXPORT NSNotificationName const NSDidBecomeSingleThreadedNotification;
// 某个线层结束
FOUNDATION_EXPORT NSNotificationName const NSThreadWillExitNotification;
四、NSThread+Runloop实现常驻线程
NSThread在实际开发中,比较常用到的场景就是实现常驻线程。
由于每次开辟子线程都会消耗CPU,所以频繁开启子线程会消耗大量CPU,而且创建的子线程都是任务执行后,就会被释放,不能再次利用。
常驻线程,就是一个使用完以后,不会释放,可以再次利用的线程。
使用NSThead+Runloop实现常驻线程,示例代码如下:
- (void)viewDidLoad {
[super viewDidLoad];
[self performSelector:@selector(test) onThread:[self shareThread] withObject:nil waitUntilDone:NO];
}
- (void)test {
NSLog(@"test:%@", [NSThread currentThread]);
}
// 创建一个NSThread单例
- (NSThread *)shareThread {
static NSThread *shareThread = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
shareThread = [[NSThread alloc] initWithTarget:self selector:@selector(threadTest) object:nil];
[shareThread setName:@"MyThread"];
[shareThread start];
});
return shareThread;
}
- (void)threadTest {
@autoreleasepool {
NSRunLoop *runloop = [NSRunLoop currentRunLoop];
[runloop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
[runloop run];
}
}
网友评论