NSThread 是苹果官方提供的,可以直接操作线程对象。不过也需要需要程序员自己管理线程的生命周期(主要是创建),我们在开发的过程中偶尔使用 NSThread。比如我们会经常调用[NSThread currentThread]来显示当前的进程信息。
NSThread的创建
NSThread 有多种创建方式。
方式一 :创建线程并手动使用 start 启动线程
- (instancetype)initWithTarget:(id)target selector:(SEL)selector object:(nullable id)argument
- (instancetype)initWithBlock:(void (^)(void))block
上述两个方法是创建线程的方法,线程创建好以后,需要我们手动调用 start 方法来启动线程。
// 1. 创建线程
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil];
// 2. 启动线程
[thread start]; // 线程一启动,就会在线程thread中执行self的run方法
- (void)run {
...
}
方式二 :创建线程且自动启动线程
+ (void)detachNewThreadSelector:(SEL)selector toTarget:(id)target withObject:(nullable id)argument
+ (void)detachNewThreadWithBlock:(void (^)(void))block
利用这两种方法创建的线程,不需要手动启动线程,会自动启动线程调用run方法。
[NSThread detachNewThreadSelector:@selector(run) toTarget:self withObject:nil];
// 新线程自动调用方法,里边为需要执行的任务
- (void)run {
NSLog(@"%@", [NSThread currentThread]);
}
方式三 :隐式创建线程(NSObject 的方法)
// 在主线程上执行操作
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait modes:(nullable NSArray<NSString *> *)array;
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait;
// equivalent to the first method with kCFRunLoopCommonModes
//在指定线程执行
- (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
// equivalent to the first method with kCFRunLoopCommonModes
//在后台线程(一条新的线程)执行
- (void)performSelectorInBackground:(SEL)aSelector withObject:(nullable id)arg
// 在当前线程上执行操作,调用 NSObject 的 performSelector:相关方法
- (id)performSelector:(SEL)aSelector;
- (id)performSelector:(SEL)aSelector withObject:(id)object;
- (id)performSelector:(SEL)aSelector withObject:(id)object1 withObject:(id)object2;
//延迟执行,NSRunLoop 相关,如果是子线程执行,需要开启runloop
//将延迟指定为0并不一定会使选择器立即执行。选择器仍在线程的运行循环中排队,并尽快执行。
- (void)performSelector:(SEL)aSelector withObject:(nullable id)anArgument afterDelay:(NSTimeInterval)delay inModes:(NSArray<NSRunLoopMode> *)modes;
- (void)performSelector:(SEL)aSelector withObject:(nullable id)anArgument afterDelay:(NSTimeInterval)delay;//未指定mode,默认为NSDefaultRunLoopMode
NSThread的一些用法
// 获得主线程
+ (NSThread *)mainThread;
// 判断是否为主线程(对象方法)
- (BOOL)isMainThread;
// 判断是否为主线程(类方法)
+ (BOOL)isMainThread;
// 获得当前线程
NSThread *currentThread = [NSThread currentThread];
// 给线程设置名称——setter方法
- (void)setName:(NSString *)n;
// 获取线程的名称
- (NSString *)name;
线程的状态
线程的状态有新建(New)、就绪(Runnable)、运行(Running)、阻塞(Blocked)、死亡(Dead)状态。
当线程被创建以后会在内存中开辟一块内存存放这个线程对象,当调用了[thread start] 后这个线程会被标记为可调度的线程对象,存放在可调度线程池中,如果CPU现在调度当前线程对象,则当前线程对象进入运行状态,如果CPU调度其他线程对象,则当前线程对象回到就绪状态。 如果CPU在运行当前线程对象的时候调用了sleep方法\等待同步锁,则当前线程对象就进入了阻塞状态,等到sleep到时\得到同步锁,则回到就绪状态。 如果CPU在运行当前线程对象的时候线程任务执行完毕\异常强制退出,则当前线程对象进入死亡状态。
NSThread的状态
//使线程启动
- (void)start;
//使线程退出执行--》死亡
+ (void)exit;
//使线程阻塞
+ (void)sleepUntilDate:(NSDate *)date;
+ (void)sleepForTimeInterval:(NSTimeInterval)ti;
线程间通信
假如我需要下载一张大图图片,然后在主线程更新
- (void)downloadImage {
NSLog(@"current thread -- %@", [NSThread currentThread]);
// 1. 获取图片 imageUrl
NSURL *imageUrl = [NSURL URLWithString:@"https://aaa.jpg"];
// 2. 从 imageUrl 中读取数据(下载图片) -- 耗时操作
NSData *imageData = [NSData dataWithContentsOfURL:imageUrl];
// 通过二进制 data 创建 image
UIImage *image = [UIImage imageWithData:imageData];
// 3. 回到主线程进行图片赋值和界面刷新
[self performSelectorOnMainThread:@selector(refreshOnMainThread:) withObject:image waitUntilDone:YES]; //waitUntilDone参数的设置为YES表示需要阻塞线程等待耗时操作执行完成。
}
/**
* 回到主线程进行图片赋值和界面刷新
*/
- (void)refreshOnMainThread:(UIImage *)image {
NSLog(@"current thread -- %@", [NSThread currentThread]);
// 赋值图片到imageview
self.imageView.image = image;
}
线程安全
为了保证线程安全,需要对线程加锁,iOS 实现线程加锁有很多种方式。@synchronized、 NSLock、dispatch_semaphore等。
依然是买票问题:
/**
* 初始化火车票数量、卖票窗口(线程安全)、并开始卖票
*/
- (void)initTicketStatusSave {
// 1. 设置剩余火车票为 50
self.ticketSurplusCount = 50;
// 2. 设置北京火车票售卖窗口的线程
self.ticketSaleWindow1 = [[NSThread alloc]initWithTarget:self selector:@selector(saleTicketSafe) object:nil];
self.ticketSaleWindow1.name = @"北京火车票售票窗口";
// 3. 设置上海火车票售卖窗口的线程
self.ticketSaleWindow2 = [[NSThread alloc]initWithTarget:self selector:@selector(saleTicketSafe) object:nil];
self.ticketSaleWindow2.name = @"上海火车票售票窗口";
// 4. 开始售卖火车票
[self.ticketSaleWindow1 start];
[self.ticketSaleWindow2 start];
}
/**
* 售卖火车票(线程安全)
*/
- (void)saleTicketSafe {
while (1) {
// 互斥锁
@synchronized (self) {
//如果还有票,继续售卖
if (self.ticketSurplusCount > 0) {
self.ticketSurplusCount --;
NSLog(@"%@", [NSString stringWithFormat:@"剩余票数:%ld 窗口:%@", self.ticketSurplusCount, [NSThread currentThread].name]);
[NSThread sleepForTimeInterval:0.2];
}
//如果已卖完,关闭售票窗口
else {
NSLog(@"所有火车票均已售完");
break;
}
}
}
}