NSThread簡介
NSThread
是面向?qū)ο蟮模庋b程度最小最輕量級(jí)的,使用更靈活谨朝,但要手動(dòng)管理線程的生命周期、線程同步和線程加鎖等,開銷較大字币。NSThread
的基本使用比較簡單则披,可以動(dòng)態(tài)創(chuàng)建初始化NSThread
對(duì)象,對(duì)其進(jìn)行設(shè)置然后啟動(dòng)洗出;也可以通過NSThread
的靜態(tài)方法快速創(chuàng)建并啟動(dòng)新線程士复;此外NSObject
基類對(duì)象還提供了隱式快速創(chuàng)建NSThread
線程的performSelector
系列類別擴(kuò)展工具方法;NSThread
還提供了一些靜態(tài)工具接口來控制當(dāng)前線程以及獲取當(dāng)前線程的一些信息共苛。
為什么要用 NSThread 呢判没?
- 使用NSThread對(duì)象建立一個(gè)線程非常方便
- 但是!要使用NSThread管理多個(gè)線程非常困難隅茎,不推薦使用
- 技巧澄峰!使用[NSThread currentThread]獲得任務(wù)所在線程,適用于這三種技術(shù)
- 使線程休眠3秒:[NSThread sleepForTimeInterval:0.3f];
NSThread創(chuàng)建線程
NSThread有三種創(chuàng)建方式:
-
initWithTarget
方式辟犀,先創(chuàng)建線程對(duì)象俏竞,再啟動(dòng) (可以輕松拿到線程) -
detachNewThreadSelector
顯式創(chuàng)建并啟動(dòng)線程(快捷) -
performSelectorInBackground
隱式創(chuàng)建并啟動(dòng)線程(快捷)
/**
initWithTarget
*/
- (void)onThread1 {
// 創(chuàng)建并啟動(dòng)
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil];
// 設(shè)置線程名
[thread setName:@"thread1"];
// 設(shè)置優(yōu)先級(jí),優(yōu)先級(jí)從0到1堂竟,1最高
[thread setThreadPriority:0.9];
// 啟動(dòng)
[thread start];
}
/**
`detachNewThreadSelector`顯式創(chuàng)建并啟動(dòng)線程
*/
- (void)onThread2 {
// 使用類方法創(chuàng)建線程并自動(dòng)啟動(dòng)線程
[NSThread detachNewThreadSelector:@selector(run) toTarget:self withObject:nil];
}
/**
`performSelectorInBackground`隱式創(chuàng)建并啟動(dòng)線程
*/
- (void)onThread3 {
// 使用NSObject的方法隱式創(chuàng)建并自動(dòng)啟動(dòng)
[self performSelectorInBackground:@selector(run) withObject:nil];
}
- (void)run {
NSLog(@"當(dāng)前線程%@", [NSThread currentThread]);
}
打印結(jié)果:
NSThread方法
//獲取當(dāng)前線程
+(NSThread *)currentThread;
//創(chuàng)建線程后自動(dòng)啟動(dòng)線程
+ (void)detachNewThreadSelector:(SEL)selector toTarget:(id)target withObject:(id)argument;
//是否是多線程
+ (BOOL)isMultiThreaded;
//線程字典
- (NSMutableDictionary *)threadDictionary;
//線程休眠到什么時(shí)間
+ (void)sleepUntilDate:(NSDate *)date;
//線程休眠多久
+ (void)sleepForTimeInterval:(NSTimeInterval)ti;
//取消線程
- (void)cancel;
//啟動(dòng)線程
- (void)start;
//退出線程
+ (void)exit;
//線程優(yōu)先級(jí)
+ (double)threadPriority;
+ (BOOL)setThreadPriority:(double)p;
- (double)threadPriority NS_AVAILABLE(10_6, 4_0);
- (void)setThreadPriority:(double)p NS_AVAILABLE(10_6, 4_0);
//調(diào)用棧返回地址
+ (NSArray *)callStackReturnAddresses NS_AVAILABLE(10_5, 2_0);
+ (NSArray *)callStackSymbols NS_AVAILABLE(10_6, 4_0);
//設(shè)置線程名字
- (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);
//是否正在執(zhí)行
- (BOOL)isExecuting NS_AVAILABLE(10_5, 2_0);
//是否執(zhí)行完成
- (BOOL)isFinished NS_AVAILABLE(10_5, 2_0);
//是否取消線程
- (BOOL)isCancelled NS_AVAILABLE(10_5, 2_0);
- (void)cancel NS_AVAILABLE(10_5, 2_0);
//線程啟動(dòng)
- (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
//隱式創(chuàng)建并啟動(dòng)線程
- (void)performSelectorInBackground:(SEL)aSelector withObject:(id)arg NS_AVAILABLE(10_5, 2_0);
NSThread線程狀態(tài)
- 1魂毁、新建:實(shí)例化對(duì)象
- 2、就緒:向線程對(duì)象發(fā)送 start 消息出嘹,線程對(duì)象被加入“可調(diào)度線程池”等待 CPU 調(diào)度席楚;detach 方法和 performSelectorInBackground 方法會(huì)直接實(shí)例化一個(gè)線程對(duì)象并加入“可調(diào)度線程池”
- 3、運(yùn)行:CPU 負(fù)責(zé)調(diào)度“可調(diào)度線程池”中線程的執(zhí)行,線程執(zhí)行完成之前税稼,狀態(tài)可能會(huì)在“就緒”和“運(yùn)行”之間來回切換,“就緒”和“運(yùn)行”之間的狀態(tài)變化由 CPU 負(fù)責(zé)烦秩,程序員不能干預(yù)
- 4、阻塞:當(dāng)滿足某個(gè)預(yù)定條件時(shí)郎仆,可以使用休眠或鎖阻塞線程執(zhí)行,影響的方法有:sleepForTimeInterval只祠,sleepUntilDate,@synchronized(self)x線程鎖扰肌;
線程對(duì)象進(jìn)入阻塞狀態(tài)后抛寝,會(huì)被從“可調(diào)度線程池”中移出,CPU 不再調(diào)度 - 5曙旭、死亡
死亡方式:
5.1 正常死亡:線程執(zhí)行完畢
5.2 非正常死亡:
* 線程內(nèi)死亡--->[NSThread exit]:
強(qiáng)行中止后盗舰,后續(xù)代碼都不會(huì)在執(zhí)行
* 線程外死亡:[threadObj cancel]
--->通知線程對(duì)象取消,在線程執(zhí)行方法中需要增加 isCancelled 判斷,如果 isCancelled == YES,直接返回
死亡后線程對(duì)象的 isFinished 屬性為 YES桂躏;如果是發(fā)送 calcel 消息钻趋,線程對(duì)象的 isCancelled 屬性為YES;死亡后 stackSize == 0沼头,內(nèi)存空間被釋放爷绘。
啟動(dòng)線程
// 線程啟動(dòng)
- (void)start;
阻塞線程
// 線程休眠到某一時(shí)刻
+ (void)sleepUntilDate:(NSDate *)date;
// 線程休眠多久
+ (void)sleepForTimeInterval:(NSTimeInterval)ti;
結(jié)束線程
// 結(jié)束線程
+ (void)exit;
關(guān)于cancel
的疑問,當(dāng)使用cancel
方法時(shí)进倍,只是改變了線程的狀態(tài)標(biāo)識(shí)土至,并不能結(jié)束線程,所以我們要配合isCancelled
方法進(jìn)行使用猾昆。
- (void)onThread {
// 使用NSObject的方法隱式創(chuàng)建并自動(dòng)啟動(dòng)
[self performSelectorInBackground:@selector(run) withObject:nil];
}
- (void)run {
NSLog(@"當(dāng)前線程%@", [NSThread currentThread]);
for (int i = 0 ; i < 100; i++) {
if (i == 20) {
//取消線程
[[NSThread currentThread] cancel];
NSLog(@"取消線程%@", [NSThread currentThread]);
}
if ([[NSThread currentThread] isCancelled]) {
NSLog(@"結(jié)束線程%@", [NSThread currentThread]);
//結(jié)束線程
[NSThread exit];
NSLog(@"這行代碼不會(huì)打印的");
}
}
}
打印結(jié)果:
NSThread線程間通信
在開發(fā)中陶因,我們經(jīng)常會(huì)在子線程進(jìn)行耗時(shí)操作,操作結(jié)束后再回到主線程去刷新UI垂蜗。這就涉及到了子線程和主線程之間的通信楷扬。看一下官方關(guān)于NSThread的線程間通信的方法贴见。
// 在主線程上執(zhí)行操作
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait;
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait modes:(NSArray<NSString *> *)array;
// equivalent to the first method with kCFRunLoopCommonModes
// 在指定線程上執(zhí)行操作
- (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);
// 在當(dāng)前線程上執(zhí)行操作烘苹,調(diào)用 NSObject 的 performSelector:相關(guān)方法
- (id)performSelector:(SEL)aSelector;
- (id)performSelector:(SEL)aSelector withObject:(id)object;
- (id)performSelector:(SEL)aSelector withObject:(id)object1 withObject:(id)object2;
下面通過一個(gè)經(jīng)典的下載圖片DEMO來展示線程之間的通信。具體步驟如下: 1片部、開啟一個(gè)子線程镣衡,在子線程中下載圖片。 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) {
//打印當(dāng)前線程
print("下載圖片線程", Thread.current)
//獲取圖片鏈接
guard let url = URL.init(string: urlStr) else {return}
//下載圖片二進(jìn)制數(shù)據(jù)
guard let data = try? Data.init(contentsOf: url) else {return}
//設(shè)置圖片
guard let img = UIImage.init(data: data) else {return}
//回到主線程刷新UI
self.performSelector(onMainThread: #selector(downloadFinished(_:)), with: img, waitUntilDone: false)
}
@objc func downloadFinished(_ img: UIImage) {
//打印當(dāng)前線程
print("刷新UI線程", Thread.current)
// 賦值圖片到imageview
self.imageView.image = image
}
復(fù)制代碼
NSThread線程安全
線程安全辖所,也可以被稱為線程同步惰说,主要是解決多線程爭搶操作資源的問題,就比如火車票缘回,全國各地多個(gè)售票窗口同事去售賣同一列火車票吆视。 怎么保證,多地售票的票池保持一致切诀,就需要用到多線程同步的技術(shù)去實(shí)現(xiàn)了揩环。鎖的問題統(tǒng)一到多線程鎖里詳細(xì)講解。