前言
在GCD和NSOperationQueue之前涉兽,iOS使用線程一般是用NSThread捻艳,而NSThread是對POSIX thread的封裝。使用NSThread的一個最大的問題是:直接操縱線程,線程的生死完全交給開發(fā)者來控制厂画。在大的工程中,模塊間相互獨立拷邢,如果線程數(shù)量持續(xù)增長袱院,將會導(dǎo)致難以控制的問題。
先看一段API文檔的描述
An NSThread object controls a thread of execution. Use this class when you want to have an Objective-C method run in its own thread of execution. Threads are especially useful when you need to perform a lengthy task, but don’t want it to block the execution of the rest of the application. In particular, you can use threads to avoid blocking the main thread of the application, which handles user interface and event-related actions. Threads can also be used to divide a large job into several smaller jobs, which can lead to performance increases on multi-core computers.
大概的意思是:一個NSThread對象管理一個線程的執(zhí)行瞭稼。
當(dāng)你想要將一個Objective-C方法運行在它自己獨立的線程中忽洛,可以使用這個類。當(dāng)你想執(zhí)行一個比較耗時(冗長)的操作而又不想阻塞程序其他部分的運行狀態(tài)時环肘,線程是特別有用的欲虚。尤其是你可以使用線程來避免阻塞主線程處理用戶界面以及和事件相關(guān)的活動。線程可以將待處理任務(wù)分割成小任務(wù)以提高多核計算機的性能悔雹。當(dāng)子線程的任務(wù)執(zhí)行完之后苍在,子線程會自動退出。默認(rèn)執(zhí)行[NSThread exit]方法荠商。
優(yōu)點: NSThread 比其他兩個輕量級寂恬,使用簡單
缺點: 需要自己管理線程的生命周期、線程同步莱没、加鎖初肉、睡眠以及喚醒等。線程同步對數(shù)據(jù)的加鎖會有一定的系統(tǒng)開銷饰躲。
創(chuàng)建并啟動
先創(chuàng)建線程類牙咏,再啟動
# 創(chuàng)建
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(run:) object:nil];
# 啟動
[thread start];
創(chuàng)建并自動啟動
[NSThread detachNewThreadSelector:@selector(run:) toTarget:self withObject:nil];
使用NSObject
其實NSObject直接就加入了多線程的支持,允許對象的某個方法在后臺運行嘹裂。如:
[myObj performSelectorInBackground:@selector(doSomething) withObject:nil];
線程優(yōu)先級
- (double)threadPriority;
- (BOOL)setThreadPriority:(double)p;
你創(chuàng)建的任何新線程都有一個與之關(guān)聯(lián)的默認(rèn)優(yōu)先級妄壶。內(nèi)核調(diào)度算法在決定該運
行哪個線程時,會把線程的優(yōu)先級作為考量因素寄狼,較高優(yōu)先級的線程會并不一定先運行只是比較低優(yōu)先級的線程具有更多的運行機會丁寄。
其他方法
除了創(chuàng)建啟動外,NSThread 還以很多方法泊愧,下面列舉一些常見的方法伊磺。
//取消線程
#但是需要注意在主線程中僅僅能設(shè)置線程狀態(tài),并不能真正停止當(dāng)前線程删咱,如果要終止線程必須在線程中調(diào)用exist方法屑埋,這是一個靜態(tài)方法,調(diào)用該方法可以退出當(dāng)前線程痰滋。
- (void)cancel;
//啟動線程
- (void)start;
//當(dāng)前線程不再使用時需要手動退出
+ (void)exit;
NSThread *currentThread=[NSThread currentThread];
# 如果當(dāng)前線程處于取消狀態(tài)摘能,則退出當(dāng)前線程
if (currentThread.isCancelled) {
NSLog(@"thread(%@) will be cancelled!",currentThread);
[NSThread exit];//取消當(dāng)前線程
}
//判斷某個線程的狀態(tài)的屬性
@property (readonly, getter=isExecuting) BOOL executing;
@property (readonly, getter=isFinished) BOOL finished;
@property (readonly, getter=isCancelled) BOOL cancelled;
//設(shè)置和獲取線程名字
-(void)setName:(NSString *)n;
-(NSString *)name;
//獲取當(dāng)前線程信息
+ (NSThread *)currentThread;
//獲取主線程信息
+ (NSThread *)mainThread;
//使當(dāng)前線程暫停一段時間续崖,或者暫停到某個時刻
+ (void)sleepForTimeInterval:(NSTimeInterval)time;
+ (void)sleepUntilDate:(NSDate *)date;
#它可以獲取當(dāng)前線程類,你就可以知道當(dāng)前線程的各種屬性团搞,用于調(diào)試十分方便袜刷。下面來看看它的一些用法。
#通過NSThread的currentThread可以取得當(dāng)前操作的線程莺丑,
#其中會記錄線程名稱name和編號number著蟹,需要注意主線程編號永遠(yuǎn)為1。
[NSThread currentThread]梢莽,
#回到主線程中
- (void)performSelectorOnMainThread:(SEL)aSelector 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);
#在后臺執(zhí)行一個操作,本質(zhì)就是重新創(chuàng)建一個線程執(zhí)行當(dāng)前方法昏名。
- (void)performSelectorInBackground:(SEL)aSelector...
一個用NSThread下載圖片的例子
#pragma mark 多線程下載圖片
-(void)loadImageWithMultiThread{
#創(chuàng)建多個線程用于填充圖片
for (int i=0; i<ROW_COUNT*COLUMN_COUNT; ++i) {
// [NSThread detachNewThreadSelector:@selector(loadImage:) toTarget:self withObject:[NSNumber numberWithInt:i]];
NSThread *thread=[[NSThread alloc]initWithTarget:self selector:@selector(loadImage:) object:[NSNumber numberWithInt:i]];
thread.name=[NSString stringWithFormat:@"myThread%i",i];//設(shè)置線程名稱
[thread start];
}
}
#pragma mark 加載圖片
-(void)loadImage:(NSNumber *)index{
// NSLog(@"%i",i);
//currentThread方法可以取得當(dāng)前操作線程
NSLog(@"current thread:%@",[NSThread currentThread]);
int i=[index integerValue];
// NSLog(@"%i",i);//未必按順序輸出
NSData *data= [self requestData:i];
KCImageData *imageData=[[KCImageData alloc]init];
imageData.index=i;
imageData.data=data;
[self performSelectorOnMainThread:@selector(updateImage:) withObject:imageData waitUntilDone:YES];
}
#pragma mark 請求圖片數(shù)據(jù)
-(NSData *)requestData:(int )index{
//對于多線程操作建議把線程操作放到@autoreleasepool中
@autoreleasepool {
//對非最后一張圖片加載線程休眠2秒
if (index!=(ROW_COUNT*COLUMN_COUNT-1)) {
#讓不滿足條件的線程都休眠涮雷。你就會看到最后一張圖片總是第一個加載(除非網(wǎng)速特別差)。
[NSThread sleepForTimeInterval:2.0];
}
NSURL *url=[NSURL URLWithString:_imageNames[index]];
NSData *data=[NSData dataWithContentsOfURL:url];
return data;
}
}
#pragma mark 將圖片顯示到界面
-(void)updateImage:(NSData *)imageData{
UIImage *image=[UIImage imageWithData:imageData];
_imageView.image=image;
}
NSThread 的調(diào)試意義
首先要說明一下的是轻局,類似[[NSThread currentThread] name] 這樣獲取到NSThread屬性的操作只對 創(chuàng)建的NSThread類有效洪鸭,對其他多線程(比如通過dispatch_queue_create 創(chuàng)建的線程)并不能獲取到當(dāng)前線程的名稱。
我們在調(diào)試多線程的時 [NSThread currentThread] 可以獲取到基本的線程信息仑扑,比如<NSThread: 0x60800006c900>{number = 1, name = main}|<NSThread: 0x6000000741c0>{number = 3, name = (null)} 主線程的 number永遠(yuǎn)是1览爵,name永遠(yuǎn)是 main,其他線程的name一般都為 (null)
通過dispatch_queue_t asd = dispatch_queue_create("信息", DISPATCH_QUEUE_CONCURRENT);創(chuàng)建的線程我們可以通過 const char *
dispatch_queue_get_label(dispatch_queue_t _Nullable queue);來獲取到我們創(chuàng)建時起的名稱
- (void)logThread :(dispatch_queue_t)t
{
NSLog(@"current thread:%@ %@",[NSThread currentThread],[NSString stringWithUTF8String:dispatch_queue_get_label(t)]);
// current thread:<NSThread: 0x6000000716c0>{number = 1, name = main} 信息
}
簡單實現(xiàn)圖片下載的效果
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
// 在子線程中調(diào)用download方法下載圖片
[self performSelectorInBackground:@selector(download) withObject:nil];
}
-(void)download
{
//1.根據(jù)URL下載圖片
//從網(wǎng)絡(luò)中下載圖片
NSURL *urlstr=[NSURL URLWithString:@"fdsf"];
//把圖片轉(zhuǎn)換為二進(jìn)制的數(shù)據(jù)
NSData *data=[NSData dataWithContentsOfURL:urlstr];//這一行操作會比較耗時
//把數(shù)據(jù)轉(zhuǎn)換成圖片
UIImage *image=[UIImage imageWithData:data];
//2.回到主線程中設(shè)置圖片
[self performSelectorOnMainThread:@selector(settingImage:) withObject:image waitUntilDone:NO];
}
//設(shè)置顯示圖片
-(void)settingImage:(UIImage *)image
{
self.iconView.image=image;
}
異步和多線程并不是一個同等關(guān)系,異步是最終目的,多線程只是我們實現(xiàn)異步的一種手段镇饮。異步是當(dāng)一個調(diào)用請求發(fā)送給被調(diào)用者,而調(diào)用者不用等待其結(jié)果的返回而可以做其它的事情蜓竹。實現(xiàn)異步可以采用多線程技術(shù)或則交給另外的進(jìn)程來處理。
本文參考文章
IOS多線程開發(fā)其實很簡單