通常在iOS中會遇到四種多線程編程的技術(shù),分別是:
(一)pthread
(二)NSThread
(三)NSOperation
(四)GCD(全稱:Grand Central Dispatch,又譯為“牛逼的中樞調(diào)度器”)

pthread其實不用多說,因為是C語言的,所以在OC中使用十分不便,幾乎不用。NSThread這套方案是經(jīng)過蘋果封裝后,并且完全面向?qū)ο蟮?。所以你可以直接操控線程對象,非常直觀和方便。不過它的生命周期還是需要我們手動管理,所以實際上使用也比較少,使用頻率較多的是GCD以及NSOperation。
下面先來介紹一下NSThread。
NSThread 有四種直接創(chuàng)建方式:
//方法一
//優(yōu)點:能拿到線程對象 缺點:需要手動的啟動線程
NSThread *thread = [[NSThread alloc]initWithTarget:self selector:@selector(run:) object:@"ios"];
//02 啟動線程
[thread start];
//方法二
//優(yōu)點:自動啟動線程 缺點:不能拿到線程對象
[NSThread detachNewThreadSelector:@selector(run:) toTarget:self withObject:nil];
//方法三 開啟一條后臺線程
//優(yōu)點:自動啟動線程 缺點:不能拿到線程對象
[self performSelectorInBackground:@selector(run:) withObject:@"后臺線程"];
//方法四 自定義
NewThread *threadB = [[NewThread alloc]init];
[threadB start];
//自定義的優(yōu)點
-(void)main
{
NSLog(@"重寫main方法封裝任務--%@",[NSThread currentThread]);
}
如果是先創(chuàng)建線程對象,然后再運行線程操作,在運行線程操作前可以設(shè)置線程的優(yōu)先級等線程信息。 設(shè)置優(yōu)先級 0~1.0 默認是0.5 最高是1.0。
判斷線程是否是主線程
//1.number == 1 主線程 != 1 子線程
//2.對象方法來判斷某個線程是否是主線程
NSThread *currentThread = [NSThread currentThread];
NSLog(@"%zd",[currentThread isMainThread]);
//3.判斷當前線程是否是主線程
NSLog(@"%zd",[NSThread isMainThread]);
這里有個不得不提的知識點那就是線程的安全,那么也要提到一個關(guān)于賣票的經(jīng)典案例和同步鎖。
首先定義100張票以及三個線程售票員
self.totalCount = 100;
//01 創(chuàng)建三個線程對象(售票員)
self.thread01 = [[NSThread alloc]initWithTarget:self selector:@selector(saleTicket) object:nil];
self.thread02 = [[NSThread alloc]initWithTarget:self selector:@selector(saleTicket) object:nil];
self.thread03 = [[NSThread alloc]initWithTarget:self selector:@selector(saleTicket) object:nil];
//02 設(shè)置名稱
self.thread01.name = @"售票員A";
self.thread02.name = @"售票員B";
self.thread03.name = @"售票員C";
//03 啟動線程
[self.thread01 start];
[self.thread02 start];
[self.thread03 start];
然后提供一個售票、計算剩余票數(shù)的方法
-(void)saleTicket
{
while (1) {
//檢查余票
NSInteger count = self.totalCount;
if (count >0) {
//賣出去一張
self.totalCount = count - 1;
NSLog(@"%@賣出一張票,還剩%zd張票",[NSThreadcurrentThread].name,self.totalCount);
}else{
NSLog(@"%@票已經(jīng)賣完了",[NSThread currentThread].name);
break;
}
}
}

通過觀察打印結(jié)果可知,如果沒有進行其他處理的話,多線程同時爭奪同一塊資源將會造成線程的不安全。在這里的直觀表現(xiàn)為順序的錯亂,以及同一張票被售出多次的情況,所以這種情況下我們需要引入同步鎖來保證線程安全。加鎖方法如下:
-(void)saleTicket
{
while (1) {
//OC中的同步鎖:(鎖對象) + {要鎖住的代碼}
//鎖對象:要求是全局唯一的屬性
@synchronized (self) {
//檢查余票
NSInteger count = self.totalCount;
if (count >0) {
//賣出去一張
self.totalCount = count - 1;
NSLog(@"%@賣出去了一張票還剩下%zd張票",[NSThread currentThread].name,self.totalCount);
}else
{
NSLog(@"%@發(fā)現(xiàn)票已經(jīng)賣完了",[NSThread currentThread].name);
break;
}
}
}
}
此處有兩個注意點:1)要注意加鎖的位置 2)加鎖需要耗費性能,因此需要注意加鎖的條件(多線程訪問同一塊資源)
當然,NSThread還可以用來做線程間通訊,比如下載圖片并展示為例,將下載耗時操作放在子線程,下載完成后再切換回主線程在UI界面對圖片進行展示
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
[NSThread detachNewThreadSelector:@selector(download) toTarget:self withObject:nil];
}
在download方法中進行線程切換并展示圖片:
[self.imageView performSelector:@selector(setImage:) onThread: [NSThread mainThread] withObject:image waitUntilDone:YES];
NSThread的基本介紹就到此結(jié)束,下面將會對帶來GCD和NSOperation的個人理解分享。