NSThread简介
NSThread
是面向对象的,封装程度最小最轻量级的,使用更灵活,但要手动管理线程的生命周期、线程同步和线程加锁等,开销较大。NSThread
的基本使用比较简单,可以动态创建初始化NSThread
对象,对其进行设置然后启动;也可以通过NSThread
的静态方法快速创建并启动新线程;此外NSObject
基类对象还提供了隐式快速创建NSThread
线程的performSelector
系列类别扩展工具方法;NSThread
还提供了一些静态工具接口来控制当前线程以及获取当前线程的一些信息。
为什么要用 NSThread 呢?
- 使用NSThread对象建立一个线程非常方便
- 但是!要使用NSThread管理多个线程非常困难,不推荐使用
- 技巧!使用[NSThread currentThread]获得任务所在线程,适用于这三种技术
- 使线程休眠3秒:[NSThread sleepForTimeInterval:0.3f];
NSThread创建线程
NSThread有三种创建方式:
-
initWithTarget
方式,先创建线程对象,再启动 (可以轻松拿到线程) -
detachNewThreadSelector
显式创建并启动线程(快捷) -
performSelectorInBackground
隐式创建并启动线程(快捷)
/**
initWithTarget
*/
- (void)onThread1 {
// 创建并启动
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil];
// 设置线程名
[thread setName:@"thread1"];
// 设置优先级,优先级从0到1,1最高
[thread setThreadPriority:0.9];
// 启动
[thread start];
}
/**
`detachNewThreadSelector`显式创建并启动线程
*/
- (void)onThread2 {
// 使用类方法创建线程并自动启动线程
[NSThread detachNewThreadSelector:@selector(run) toTarget:self withObject:nil];
}
/**
`performSelectorInBackground`隐式创建并启动线程
*/
- (void)onThread3 {
// 使用NSObject的方法隐式创建并自动启动
[self performSelectorInBackground:@selector(run) withObject:nil];
}
- (void)run {
NSLog(@"当前线程%@", [NSThread currentThread]);
}
打印结果:
NSThread方法
//获取当前线程
+(NSThread *)currentThread;
//创建线程后自动启动线程
+ (void)detachNewThreadSelector:(SEL)selector toTarget:(id)target withObject:(id)argument;
//是否是多线程
+ (BOOL)isMultiThreaded;
//线程字典
- (NSMutableDictionary *)threadDictionary;
//线程休眠到什么时间
+ (void)sleepUntilDate:(NSDate *)date;
//线程休眠多久
+ (void)sleepForTimeInterval:(NSTimeInterval)ti;
//取消线程
- (void)cancel;
//启动线程
- (void)start;
//退出线程
+ (void)exit;
//线程优先级
+ (double)threadPriority;
+ (BOOL)setThreadPriority:(double)p;
- (double)threadPriority NS_AVAILABLE(10_6, 4_0);
- (void)setThreadPriority:(double)p NS_AVAILABLE(10_6, 4_0);
//调用栈返回地址
+ (NSArray *)callStackReturnAddresses NS_AVAILABLE(10_5, 2_0);
+ (NSArray *)callStackSymbols NS_AVAILABLE(10_6, 4_0);
//设置线程名字
- (void)setName:(NSString *)n NS_AVAILABLE(10_5, 2_0);
- (NSString *)name NS_AVAILABLE(10_5, 2_0);
//获取栈的大小
- (NSUInteger)stackSize NS_AVAILABLE(10_5, 2_0);
- (void)setStackSize:(NSUInteger)s NS_AVAILABLE(10_5, 2_0);
// 获得主线程
+ (NSThread *)mainThread;
//是否是主线程
- (BOOL)isMainThread NS_AVAILABLE(10_5, 2_0);
+ (BOOL)isMainThread NS_AVAILABLE(10_5, 2_0); // reports whether current thread is main
+ (NSThread *)mainThread NS_AVAILABLE(10_5, 2_0);
//初始化方法
- (id)init NS_AVAILABLE(10_5, 2_0); // designated initializer
- (id)initWithTarget:(id)target selector:(SEL)selector object:(id)argument NS_AVAILABLE(10_5, 2_0);
//是否正在执行
- (BOOL)isExecuting NS_AVAILABLE(10_5, 2_0);
//是否执行完成
- (BOOL)isFinished NS_AVAILABLE(10_5, 2_0);
//是否取消线程
- (BOOL)isCancelled NS_AVAILABLE(10_5, 2_0);
- (void)cancel NS_AVAILABLE(10_5, 2_0);
//线程启动
- (void)start NS_AVAILABLE(10_5, 2_0);
- (void)main NS_AVAILABLE(10_5, 2_0); // thread body method
@end
//多线程通知
FOUNDATION_EXPORT NSString * const NSWillBecomeMultiThreadedNotification;
FOUNDATION_EXPORT NSString * const NSDidBecomeSingleThreadedNotification;
FOUNDATION_EXPORT NSString * const NSThreadWillExitNotification;
@interface NSObject (NSThreadPerformAdditions)
//与主线程通信
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait modes:(NSArray *)array;
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait;
// equivalent to the first method with kCFRunLoopCommonModes
//与其他子线程通信
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait modes:(NSArray *)array NS_AVAILABLE(10_5, 2_0);
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait NS_AVAILABLE(10_5, 2_0);
// equivalent to the first method with kCFRunLoopCommonModes
//隐式创建并启动线程
- (void)performSelectorInBackground:(SEL)aSelector withObject:(id)arg NS_AVAILABLE(10_5, 2_0);
NSThread线程状态
- 1、新建:实例化对象
- 2、就绪:向线程对象发送 start 消息,线程对象被加入“可调度线程池”等待 CPU 调度;detach 方法和 performSelectorInBackground 方法会直接实例化一个线程对象并加入“可调度线程池”
- 3、运行:CPU 负责调度“可调度线程池”中线程的执行,线程执行完成之前,状态可能会在“就绪”和“运行”之间来回切换,“就绪”和“运行”之间的状态变化由 CPU 负责,程序员不能干预
- 4、阻塞:当满足某个预定条件时,可以使用休眠或锁阻塞线程执行,影响的方法有:sleepForTimeInterval,sleepUntilDate,@synchronized(self)x线程锁;
线程对象进入阻塞状态后,会被从“可调度线程池”中移出,CPU 不再调度 - 5、死亡
死亡方式:
5.1 正常死亡:线程执行完毕
5.2 非正常死亡:
* 线程内死亡--->[NSThread exit]:
强行中止后,后续代码都不会在执行
* 线程外死亡:[threadObj cancel]
--->通知线程对象取消,在线程执行方法中需要增加 isCancelled 判断,如果 isCancelled == YES,直接返回
死亡后线程对象的 isFinished 属性为 YES;如果是发送 calcel 消息,线程对象的 isCancelled 属性为YES;死亡后 stackSize == 0,内存空间被释放。
启动线程
// 线程启动
- (void)start;
阻塞线程
// 线程休眠到某一时刻
+ (void)sleepUntilDate:(NSDate *)date;
// 线程休眠多久
+ (void)sleepForTimeInterval:(NSTimeInterval)ti;
结束线程
// 结束线程
+ (void)exit;
关于cancel
的疑问,当使用cancel
方法时,只是改变了线程的状态标识,并不能结束线程,所以我们要配合isCancelled
方法进行使用。
- (void)onThread {
// 使用NSObject的方法隐式创建并自动启动
[self performSelectorInBackground:@selector(run) withObject:nil];
}
- (void)run {
NSLog(@"当前线程%@", [NSThread currentThread]);
for (int i = 0 ; i < 100; i++) {
if (i == 20) {
//取消线程
[[NSThread currentThread] cancel];
NSLog(@"取消线程%@", [NSThread currentThread]);
}
if ([[NSThread currentThread] isCancelled]) {
NSLog(@"结束线程%@", [NSThread currentThread]);
//结束线程
[NSThread exit];
NSLog(@"这行代码不会打印的");
}
}
}
打印结果:
NSThread线程间通信
在开发中,我们经常会在子线程进行耗时操作,操作结束后再回到主线程去刷新UI。这就涉及到了子线程和主线程之间的通信。看一下官方关于NSThread的线程间通信的方法。
// 在主线程上执行操作
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait;
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait modes:(NSArray *)array;
// equivalent to the first method with kCFRunLoopCommonModes
// 在指定线程上执行操作
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait modes:(NSArray *)array NS_AVAILABLE(10_5, 2_0);
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait NS_AVAILABLE(10_5, 2_0);
// 在当前线程上执行操作,调用 NSObject 的 performSelector:相关方法
- (id)performSelector:(SEL)aSelector;
- (id)performSelector:(SEL)aSelector withObject:(id)object;
- (id)performSelector:(SEL)aSelector withObject:(id)object1 withObject:(id)object2;
下面通过一个经典的下载图片DEMO来展示线程之间的通信。具体步骤如下: 1、开启一个子线程,在子线程中下载图片。 2、回到主线程刷新UI,将图片展示在UIImageView
中。
func onThread() {
let urlStr = "http://tupian.aladd.net/2015/7/2941.jpg"
self.performSelector(inBackground: #selector(downloadImg(_:)), with: urlStr)
}
@objc func downloadImg(_ urlStr: String) {
//打印当前线程
print("下载图片线程", Thread.current)
//获取图片链接
guard let url = URL.init(string: urlStr) else {return}
//下载图片二进制数据
guard let data = try? Data.init(contentsOf: url) else {return}
//设置图片
guard let img = UIImage.init(data: data) else {return}
//回到主线程刷新UI
self.performSelector(onMainThread: #selector(downloadFinished(_:)), with: img, waitUntilDone: false)
}
@objc func downloadFinished(_ img: UIImage) {
//打印当前线程
print("刷新UI线程", Thread.current)
// 赋值图片到imageview
self.imageView.image = image
}
复制代码
NSThread线程安全
线程安全,也可以被称为线程同步,主要是解决多线程争抢操作资源的问题,就比如火车票,全国各地多个售票窗口同事去售卖同一列火车票。 怎么保证,多地售票的票池保持一致,就需要用到多线程同步的技术去实现了。锁的问题统一到多线程锁里详细讲解。