通常在iOS中會遇到四種多線程編程的技術(shù),分別是:
(一)pthread
(二)NSThread
(三)NSOperation
(四)GCD(全稱:Grand Central Dispatch答渔,又譯為“牛逼的中樞調(diào)度器”)
pthread其實(shí)不用多說关带,因?yàn)槭荂語言的,所以在OC中使用十分不便沼撕,幾乎不用宋雏。NSThread這套方案是經(jīng)過蘋果封裝后,并且完全面向?qū)ο蟮奈癫颉K阅憧梢灾苯硬倏鼐€程對象拨扶,非常直觀和方便于样。不過它的生命周期還是需要我們手動管理,所以實(shí)際上使用也比較少,使用頻率較多的是GCD以及NSOperation糊昙。
下面先來介紹一下NSThread寒瓦。
NSThread 有四種直接創(chuàng)建方式:
//方法一
//優(yōu)點(diǎn):能拿到線程對象 缺點(diǎn):需要手動的啟動線程
NSThread *thread = [[NSThread alloc]initWithTarget:self selector:@selector(run:) object:@"ios"];
//02 啟動線程
[thread start];
//方法二
//優(yōu)點(diǎn):自動啟動線程 缺點(diǎn):不能拿到線程對象
[NSThread detachNewThreadSelector:@selector(run:) toTarget:self withObject:nil];
//方法三 開啟一條后臺線程
//優(yōu)點(diǎn):自動啟動線程 缺點(diǎn):不能拿到線程對象
[self performSelectorInBackground:@selector(run:) withObject:@"后臺線程"];
//方法四 自定義
NewThread *threadB = [[NewThread alloc]init];
[threadB start];
//自定義的優(yōu)點(diǎn)
-(void)main
{
NSLog(@"重寫main方法封裝任務(wù)--%@",[NSThread currentThread]);
}
如果是先創(chuàng)建線程對象逛拱,然后再運(yùn)行線程操作邻遏,在運(yùn)行線程操作前可以設(shè)置線程的優(yōu)先級等線程信息。 設(shè)置優(yōu)先級 0~1.0 默認(rèn)是0.5 最高是1.0汹桦。
判斷線程是否是主線程
//1.number == 1 主線程 != 1 子線程
//2.對象方法來判斷某個線程是否是主線程
NSThread *currentThread = [NSThread currentThread];
NSLog(@"%zd",[currentThread isMainThread]);
//3.判斷當(dāng)前線程是否是主線程
NSLog(@"%zd",[NSThread isMainThread]);
這里有個不得不提的知識點(diǎn)那就是線程的安全鲁驶,那么也要提到一個關(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];
然后提供一個售票舞骆、計(jì)算剩余票數(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é)果可知,如果沒有進(jìn)行其他處理的話督禽,多線程同時爭奪同一塊資源將會造成線程的不安全脆霎。在這里的直觀表現(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;
}
}
}
}
此處有兩個注意點(diǎn):1)要注意加鎖的位置 2)加鎖需要耗費(fèi)性能,因此需要注意加鎖的條件(多線程訪問同一塊資源)
當(dāng)然,NSThread還可以用來做線程間通訊,比如下載圖片并展示為例玖院,將下載耗時操作放在子線程菠红,下載完成后再切換回主線程在UI界面對圖片進(jìn)行展示
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
[NSThread detachNewThreadSelector:@selector(download) toTarget:self withObject:nil];
}
在download方法中進(jìn)行線程切換并展示圖片:
[self.imageView performSelector:@selector(setImage:) onThread: [NSThread mainThread] withObject:image waitUntilDone:YES];
NSThread的基本介紹就到此結(jié)束,下面將會對帶來GCD和NSOperation的個人理解分享难菌。