談iOS多線程(NSThread哄尔、NSOperation、GCD)編程

文章配圖

一周六早上柠并,小明處于安全考慮岭接,去銀行服務(wù)廳申請多一張銀行卡作為手機消費指定數(shù)額不多的專用卡。到了銀行鸣戴,看到大廳坐滿了人粘拾,唱K的唱K窄锅,念經(jīng)的念經(jīng)缰雇,嘔奶的嘔奶入偷,彼起此伏,聲聲入耳盯串,直趕清華大學(xué)演奏團演奏的《小蘋果》戒良,呀~体捏!其實真實的情況是:每個人都做著椅子上低下頭盯著各自的手機,小明也不例外糯崎,找了個角落几缭,瀏覽起3016年的新聞沃呢。半個小時過去了,40分鐘過去了某抓,一個小時過去!小明等怒了汉矿,大喊“嘿嘿嘿,開多一條線程不可以嗎V弈础G!”

“什么是多一條線程芭β摇昆箕?”
文章大綱

一.基本概念

計算機操作系統(tǒng)都有的基本概念,以下概念簡單方式來描述。

  1. 進程: 一個具有一定獨立功能的程序關(guān)于某個數(shù)據(jù)集合的一次運行活動肺稀。可以理解成一個運行中的應(yīng)用程序夕吻。
  2. 線程: 程序執(zhí)行流的最小單元繁仁,線程是進程中的一個實體。
  3. 同步: 只能在當(dāng)前線程按先后順序依次執(zhí)行黄虱,不開啟新線程捻浦。
  4. 異步: 可以在當(dāng)前線程開啟多個新線程執(zhí)行,可不按順序執(zhí)行朱灿。
  5. 隊列: 裝載線程任務(wù)的隊形結(jié)構(gòu)。
  6. 并發(fā): 線程執(zhí)行可以同時一起進行執(zhí)行跪楞。
  7. 串行: 線程執(zhí)行只能依次逐一先后有序的執(zhí)行。

注意:

  • 一個進程可有多個線程缕碎。
  • 一個進程可有多個隊列淋叶。
  • 隊列可分并發(fā)隊列和串行隊列。

二.iOS多線程對比

1. NSThread

每個NSThread對象對應(yīng)一個線程煞檩,真正最原始的線程处嫌。
1)優(yōu)點:NSThread 輕量級最低,相對簡單熏迹。
2)缺點:手動管理所有的線程活動凝赛,如生命周期、線程同步捆昏、睡眠等毙沾。

2. NSOperation

自帶線程管理的抽象類。
1)優(yōu)點:自帶線程周期管理寇仓,操作上可更注重自己邏輯烤宙。
2)缺點:面向?qū)ο蟮某橄箢悾荒軐崿F(xiàn)它或者使用它定義好的兩個子類:NSInvocationOperation 和 NSBlockOperation服猪。

3. GCD

Grand Central Dispatch (GCD)是Apple開發(fā)的一個多核編程的解決方法屯远。
1)優(yōu)點:最高效,避開并發(fā)陷阱坡脐。
2)缺點:基于C實現(xiàn)房揭。

4. 選擇小結(jié)

1)簡單而安全的選擇NSOperation實現(xiàn)多線程即可晌端。
2)處理大量并發(fā)數(shù)據(jù)恬砂,又追求性能效率的選擇GCD。
3)NSThread本人選擇基本上是在做些小測試上使用漆羔,當(dāng)然也可以基于此造個輪子狱掂。

三.場景選擇

  1. 圖片異步加載旅急。這種常見的場景是最常見也是必不可少的酷宵。異步加載圖片有分成兩種來說明一下讯嫂。
    第一種兆沙,在UI主線程開啟新線程按順序加載圖片,加載完成刷新UI渐裸。
    第二種装悲,依然是在主線程開啟新線程尚氛,順序不定地加載圖片,加載完成個字刷新UI属瓣。
  2. 創(chuàng)作工具上的異步讯柔。 這個跟上邊任務(wù)調(diào)度道理差不多,只是為了豐富描述粗截,有助于“舉一反三”效果捣炬。如下描述的是app創(chuàng)作小說绽榛。
    場景一灭美,app本地創(chuàng)作10個章節(jié)內(nèi)容未成同步服務(wù)器昂利,接著同時發(fā)表這10個章節(jié)將產(chǎn)生的一系列動作,其中上傳內(nèi)容犁苏,獲取分配章節(jié)Id窝撵,如果后臺沒有做處理最好方式做異步按順序執(zhí)行。
    場景二短曾,app本地創(chuàng)作列表中有3本小說要發(fā)表赐劣,如果同時發(fā)表創(chuàng)作列表中的3本小說,自然考慮并行隊列執(zhí)行發(fā)表婉徘。

四.使用方法

第三標(biāo)題場景選擇內(nèi)容實現(xiàn)先留下一個懸念咐汞。具體實現(xiàn)還是先熟知一下各自的API先。

1. NSThread

1.1)三種實現(xiàn)開啟線程方式:
①.動態(tài)實例化

NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(loadImageSource:) object:imgUrl];
thread.threadPriority = 1;// 設(shè)置線程的優(yōu)先級(0.0 - 1.0几晤,1.0最高級)
[thread start];  

②.靜態(tài)實例化

[NSThread detachNewThreadSelector:@selector(loadImageSource:) toTarget:self withObject:imgUrl];   

③.隱式實例化

[self performSelectorInBackground:@selector(loadImageSource:) withObject:imgUrl];  

有了以上的知識點蟹瘾,可以試探了一下編寫場景選擇中的“圖片加載”的基本功能了掠手。

1.2)使用這三種方式編寫代碼

//動態(tài)創(chuàng)建線程
-(void)dynamicCreateThread{
    NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(loadImageSource:) object:imgUrl];
    thread.threadPriority = 1;// 設(shè)置線程的優(yōu)先級(0.0 - 1.0,1.0最高級)
    [thread start];
}

//靜態(tài)創(chuàng)建線程
-(void)staticCreateThread{
    [NSThread detachNewThreadSelector:@selector(loadImageSource:) toTarget:self withObject:imgUrl];
}

//隱式創(chuàng)建線程
-(void)implicitCreateThread{
    [self performSelectorInBackground:@selector(loadImageSource:) withObject:imgUrl];
}

-(void)loadImageSource:(NSString *)url{
    NSData *imgData = [NSData dataWithContentsOfURL:[NSURL URLWithString:url]];
    UIImage *image = [UIImage imageWithData:imgData];
    if (imgData!=nil) {
        [self performSelectorOnMainThread:@selector(refreshImageView:) withObject:image waitUntilDone:YES];
    }else{
        NSLog(@"there no image data");
    }
}

-(void)refreshImageView:(UIImage *)image{
    [self.imageView setImage:image];
}

1.3)看先效果圖
[圖片上傳失敗...(image-dd682c-1602349311823)]

1.4)NSThread的拓展認(rèn)識
①獲取當(dāng)前線程

NSThread *current = [NSThread currentThread];   

②獲取主線程

NSThread *main = [NSThread mainThread];   

③暫停當(dāng)前線程

[NSThread sleepForTimeInterval:2];  

④線程之間通信

//在指定線程上執(zhí)行操作
[self performSelector:@selector(run) onThread:thread withObject:nil waitUntilDone:YES]; 
//在主線程上執(zhí)行操作
[self performSelectorOnMainThread:@selector(run) withObject:nil waitUntilDone:YES]; 
//在當(dāng)前線程執(zhí)行操作
[self performSelector:@selector(run) withObject:nil]; 

顯然動態(tài)創(chuàng)建線程多了幾行代碼,其實就是那幾行代碼,如果重復(fù)編寫數(shù)遍那是一件多么不爽的事情株搔。首次看來靜態(tài)方法創(chuàng)作線程和隱式創(chuàng)建線程顯得比較方便纯蛾,簡潔。從知識結(jié)構(gòu)來說炮姨,講到這里應(yīng)該講述一下線程鎖碰煌,鑒于并不常用和文章過長就不在此詳細(xì)講述,有興趣可以自行查閱蛾派。

2. NSOperation

主要的實現(xiàn)方式:結(jié)合NSOperation和NSOperationQueue實現(xiàn)多線程編程个少。

  • 實例化NSOperation的子類夜焦,綁定執(zhí)行的操作。
  • 創(chuàng)建NSOperationQueue隊列茫经,將NSOperation實例添加進來卸伞。
  • 系統(tǒng)會自動將NSOperationQueue隊列中檢測取出和執(zhí)行NSOperation的操作。

2.1)使用NSOperation的子類實現(xiàn)創(chuàng)作線程荤傲。
①.NSInvocationOperation創(chuàng)建線程弃酌。

NSInvocationOperation *invocationOperation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(loadImageSource:) object:imgUrl];
//[invocationOperation start];//直接會在當(dāng)前線程主線程執(zhí)行
NSOperationQueue *queue = [[NSOperationQueue alloc]init];
[queue addOperation:invocationOperation];  

②.NSBlockOperation創(chuàng)建線程

NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{
    [self loadImageSource:imgUrl];
}];

NSOperationQueue *queue = [[NSOperationQueue alloc]init];
[queue addOperation:blockOperation];  

③.自定義NSOperation子類實現(xiàn)main方法

實現(xiàn)main方法

-(void)main {   
    // Do somthing
} 

創(chuàng)建線程實例并添加到隊列中

LoadImageOperation *imageOperation = [LoadImageOperation new];
imageOperation.loadDelegate = self;
imageOperation.imgUrl = imgUrl;

NSOperationQueue *queue = [[NSOperationQueue alloc]init];
[queue addOperation:imageOperation];

2.2)使用這三種方式編寫代碼

創(chuàng)建各個實例并添加到隊列表當(dāng)中

//使用子類NSInvocationOperation
-(void)useInvocationOperation{
    NSInvocationOperation *invocationOperation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(loadImageSource:) object:imgUrl];
    //[invocationOperation start];//直接會在當(dāng)前線程主線程執(zhí)行
    NSOperationQueue *queue = [[NSOperationQueue alloc]init];
    [queue addOperation:invocationOperation];

}

//使用子類NSBlockOperation
-(void)useBlockOperation{

    NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{
        [self loadImageSource:imgUrl];
    }];

    NSOperationQueue *queue = [[NSOperationQueue alloc]init];
    [queue addOperation:blockOperation];

}
//使用繼承NSOperation
-(void)useSubclassOperation{

    LoadImageOperation *imageOperation = [LoadImageOperation new];
    imageOperation.loadDelegate = self;
    imageOperation.imgUrl = imgUrl;

    NSOperationQueue *queue = [[NSOperationQueue alloc]init];
    [queue addOperation:imageOperation];
}

-(void)loadImageSource:(NSString *)url{

    NSData *imgData = [NSData dataWithContentsOfURL:[NSURL URLWithString:url]];
    UIImage *image = [UIImage imageWithData:imgData];
    if (imgData!=nil) {
        [self performSelectorOnMainThread:@selector(refreshImageView1:) withObject:image waitUntilDone:YES];
    }else{
        NSLog(@"there no image data");
    }

}

-(void)refreshImageView1:(UIImage *)image{
    [self.loadingLb setHidden:YES];
    [self.imageView setImage:image];
}

-(void) loadImageFinish:(UIImage *)image{
    [self.loadingLb setHidden:YES];
    [self.imageView setImage:image];
}

附自定義NSOperation子類main主要代碼實現(xiàn)

- (void)main {

    if (self.isCancelled) return;
    
    NSURL *url = [NSURL URLWithString:self.imgUrl];
    NSData *imageData = [NSData dataWithContentsOfURL:url];
            
    if (self.loadDelegate!=nil&&[self.loadDelegate respondsToSelector:@selector(loadImageFinish:)]) {
        
        [(NSObject *)self.loadDelegate performSelectorOnMainThread:@selector(loadImageFinish:) withObject:image waitUntilDone:NO];
    }
}

2.3)看先效果圖
[圖片上傳失敗...(image-2f9854-1602349311823)]

3. GCD多線程

GCD是Apple開發(fā),據(jù)說高性能的多線程解決方案乌询。既然這樣,就細(xì)說一下這個解決方案唬党。
進過Nsthread和NSOperation的講述和上邊的基礎(chǔ)概念,可以開始組合用起來吧霜浴。并發(fā)隊列蓝纲、串行隊列都用起來。
3.1)分發(fā)隊列種類(dispatch queue)
①.UI主線程隊列 main queue

dispatch_get_main_queue()  

②.并行隊列g(shù)lobal dispatch queue

dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)  

這里的兩個參數(shù)得說明一下:第一個參數(shù)用于指定優(yōu)先級永丝,分別使用DISPATCH_QUEUE_PRIORITY_HIGH和DISPATCH_QUEUE_PRIORITY_LOW兩個常量來獲取高和低優(yōu)先級的兩個queue箭养;第二個參數(shù)目前未使用到毕泌,默認(rèn)0即可

③.串行隊列serial queues

dispatch_queue_create("minggo.app.com", NULL);  

3.2)6種多線程實現(xiàn)
①后臺執(zhí)行線程創(chuàng)建

dispatch_async(dispatch_get_global_queue(0, 0), ^{
    [self loadImageSource:imgUrl1];
});

②UI線程執(zhí)行(只是為了測試,長時間加載內(nèi)容不放在主線程)

dispatch_async(dispatch_get_main_queue(), ^{
    [self loadImageSource:imgUrl1];
});

③一次性執(zhí)行(常用來寫單例)

static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
    [self loadImageSource:imgUrl1];
});

④并發(fā)地執(zhí)行循環(huán)迭代

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
size_t count = 10;
dispatch_apply(count, queue, ^(size_t i) {
    NSLog(@"循環(huán)執(zhí)行第%li次",i);
    [self loadImageSource:imgUrl1];
});  

⑤延遲執(zhí)行

double delayInSeconds = 2.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
    [self loadImageSource:imgUrl1];
});  

⑥自定義dispatch_queue_t

dispatch_queue_t urls_queue = dispatch_queue_create("minggo.app.com", NULL);
dispatch_async(urls_queue, ^{
    [self loadImageSource:imgUrl1];
});

3.3)對比多任務(wù)執(zhí)行
異步加載圖片是大部分app都要面對的問題蛇耀,那么加載圖片是按循序加載完之后才刷新UI呢坎弯?還是不安順序加載UI呢?顯然大部分的希望各自加載各自的圖片撩炊,各自刷新崎脉。以下就是模擬這兩種場景。

①先后執(zhí)行骆膝,加載兩張圖片為例

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    
    UIImage *image1 = [self loadImage:imgUrl1];
    UIImage *image2 = [self loadImage:imgUrl2];
    
    dispatch_async(dispatch_get_main_queue(), ^{
        self.imageview1.image = image1;
        self.imageView2.image = image2;
    });
});  

②并行隊列執(zhí)行灶体,也是以加載兩張圖片為例

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);


dispatch_async(queue, ^{
    
    dispatch_group_t group = dispatch_group_create();
    
    __block UIImage *image1 = nil;
    __block UIImage *image2 = nil;
    
    
    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        image1 = [self loadImage:imgUrl1];
    });
    
    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        image2 = [self loadImage:imgUrl2];
    });
    
    
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        self.imageview1.image = image1;
        self.imageView2.image = image2;
        
    });
});

①中等到兩張圖片加載完成后一起刷新蝎抽,②就是典型的異步并行的例子,不需要理會各自圖片加載的先后問題养交,完成加載圖片刷新UI即可。從加載圖片中來說灰羽,第①種不太合適使用破花,但是對于在上邊場景選擇中的創(chuàng)作工具來說有很大的好處,首先得異步進行前鹅,然后異步中有得按順序執(zhí)行幾個任務(wù)峭梳,比如上傳章節(jié)內(nèi)容。因此捂寿,我們可以靈活考慮使用這兩多線程任務(wù)執(zhí)行方式孵运,實現(xiàn)各種場景。
3.4)編碼實現(xiàn)
以上3.3的內(nèi)容99%代碼一樣治笨,就不提供一個稍微整體的代碼了旷赖。看看下邊的效果圖吧等孵。
3.5)效果圖如下
[圖片上傳失敗...(image-8a40d6-1602349311823)]

五.源碼地址

https://github.com/minggo620/iOSMutipleThread.git
如果小明這么跟銀行柜臺的MM講多線程俯萌,會不會。咐熙。糖声。“給我滾出去~~”蘸泻。

【原創(chuàng)出品 未經(jīng)授權(quán) 禁止轉(zhuǎn)載】
【歡迎微友分享轉(zhuǎn)發(fā) 禁止公號等未經(jīng)授權(quán)的轉(zhuǎn)載】

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末悦施,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子穷蛹,更是在濱河造成了極大的恐慌昼汗,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,183評論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蛙吏,死亡現(xiàn)場離奇詭異鸦做,居然都是意外死亡谓着,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,850評論 3 399
  • 文/潘曉璐 我一進店門治筒,熙熙樓的掌柜王于貴愁眉苦臉地迎上來改抡,“玉大人阿纤,你說我怎么就攤上這事∏肥埃” “怎么了?”我有些...
    開封第一講書人閱讀 168,766評論 0 361
  • 文/不壞的土叔 我叫張陵,是天一觀的道長荆忍。 經(jīng)常有香客問我撤缴,道長屈呕,這世上最難降的妖魔是什么棺亭? 我笑而不...
    開封第一講書人閱讀 59,854評論 1 299
  • 正文 為了忘掉前任,我火速辦了婚禮嗽桩,結(jié)果婚禮上凄敢,老公的妹妹穿的比我還像新娘。我一直安慰自己种樱,他們只是感情好俊卤,可當(dāng)我...
    茶點故事閱讀 68,871評論 6 398
  • 文/花漫 我一把揭開白布消恍。 她就那樣靜靜地躺著,像睡著了一般狠怨。 火紅的嫁衣襯著肌膚如雪佣赖。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,457評論 1 311
  • 那天外傅,我揣著相機與錄音俩檬,去河邊找鬼。 笑死技竟,一個胖子當(dāng)著我的面吹牛屈藐,可吹牛的內(nèi)容都是我干的熙尉。 我是一名探鬼主播搓扯,決...
    沈念sama閱讀 40,999評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了爱态?” 一聲冷哼從身側(cè)響起境钟,我...
    開封第一講書人閱讀 39,914評論 0 277
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎洞渔,沒想到半個月后缚态,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,465評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡浆熔,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,543評論 3 342
  • 正文 我和宋清朗相戀三年医增,在試婚紗的時候發(fā)現(xiàn)自己被綠了老虫。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,675評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡忽刽,死狀恐怖菊卷,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情歉甚,我是刑警寧澤扑眉,帶...
    沈念sama閱讀 36,354評論 5 351
  • 正文 年R本政府宣布,位于F島的核電站聘裁,受9級特大地震影響衡便,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜镣陕,卻給世界環(huán)境...
    茶點故事閱讀 42,029評論 3 335
  • 文/蒙蒙 一呆抑、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧厌殉,春花似錦侈咕、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,514評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至帽揪,卻和暖如春转晰,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背查邢。 一陣腳步聲響...
    開封第一講書人閱讀 33,616評論 1 274
  • 我被黑心中介騙來泰國打工扰藕, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人未桥。 一個月前我還...
    沈念sama閱讀 49,091評論 3 378
  • 正文 我出身青樓,卻偏偏與公主長得像舌菜,于是被迫代替她去往敵國和親亦镶。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,685評論 2 360

推薦閱讀更多精彩內(nèi)容