本文為投稿文章,作者:minggo
一周六早上,小明處於安全考慮,去銀行服務廳申請多一張銀行卡作為手機消費指定數額不多的專用卡。到了銀行,看到大廳坐滿了人,唱K的唱K,念經的念經,嘔奶的嘔奶,彼起此伏,聲聲入耳,直趕清華大學演奏團演奏的《小蘋果》,呀~!其實真實的情況是:每個人都做著椅子上低下頭盯著各自的手機,小明也不例外,找了個角落,浏覽起3016年的新聞。半個小時過去了,40分鐘過去了,一個小時過去!小明等怒了,大喊“嘿嘿嘿,開多一條線程不可以嗎!!!”
“什麼是多一條線程啊?”
文章大綱
一、基本概念
計算機操作系統都有的基本概念,以下概念簡單方式來描述。
進程: 一個具有一定獨立功能的程序關於某個數據集合的一次運行活動。可以理解成一個運行中的應用程序。
線程: 程序執行流的最小單元,線程是進程中的一個實體。
同步: 只能在當前線程按先後順序依次執行,不開啟新線程。
異步: 可以在當前線程開啟多個新線程執行,可不按順序執行。
隊列: 裝載線程任務的隊形結構。
並發: 線程執行可以同時一起進行執行。
串行: 線程執行只能依次逐一先後有序的執行。
注意:
一個進程可有多個線程。
一個進程可有多個隊列。
隊列可分並發隊列和串行隊列。
二、iOS多線程對比
1. NSThread
每個NSThread對象對應一個線程,真正最原始的線程。
1)優點:NSThread 輕量級最低,相對簡單。
2)缺點:手動管理所有的線程活動,如生命周期、線程同步、睡眠等。
2. NSOperation
自帶線程管理的抽象類。
1)優點:自帶線程周期管理,操作上可更注重自己邏輯。
2)缺點:面向對象的抽象類,只能實現它或者使用它定義好的兩個子類:NSInvocationOperation 和 NSBlockOperation。
3. GCD
Grand Central Dispatch (GCD)是Apple開發的一個多核編程的解決方法。
1)優點:最高效,避開並發陷阱。
2)缺點:基於C實現。
4. 選擇小結
1)簡單而安全的選擇NSOperation實現多線程即可。
2)處理大量並發數據,又追求性能效率的選擇GCD。
3)NSThread本人選擇基本上是在做些小測試上使用,當然也可以基於此造個輪子。
三、場景選擇
圖片異步加載。這種常見的場景是最常見也是必不可少的。異步加載圖片有分成兩種來說明一下。
第一種,在UI主線程開啟新線程按順序加載圖片,加載完成刷新UI。
第二種,依然是在主線程開啟新線程,順序不定地加載圖片,加載完成個字刷新UI。
創作工具上的異步。 這個跟上邊任務調度道理,只是為了豐富描述,有助於“舉一反三”效果。如下描述的是app創作小說。
場景一,app本地創作10個章節內容未成同步服務器,同時發表這10個章節產生的一系列動作,其中上傳內容,獲取分配章節Id,如何後台沒有做處理最好方式做異步按順序執行。
場景二,app本地創作列表中有3本小說為發表,同時發表創作列表中的3本小說,自然考慮並行隊列執行發表。
四、使用方法
第三標題場景選擇內容實現先留下一個懸念。具體實現還是先熟知一下各自的API先。
1. NSThread
1.1)三種實現開啟線程方式:
①.動態實例化
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(loadImageSource:) object:imgUrl]; thread.threadPriority = 1;// 設置線程的優先級(0.0 - 1.0,1.0最高級) [thread start];
②.靜態實例化
[NSThread detachNewThreadSelector:@selector(loadImageSource:) toTarget:self withObject:imgUrl];
③.隱式實例化
[self performSelectorInBackground:@selector(loadImageSource:) withObject:imgUrl];
有了以上的知識點,可以試探了一下編寫場景選擇中的“圖片加載”的基本功能了。
1.2)使用這三種方式編寫代碼
//動態創建線程 -(void)dynamicCreateThread{ NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(loadImageSource:) object:imgUrl]; thread.threadPriority = 1;// 設置線程的優先級(0.0 - 1.0,1.0最高級) [thread start]; } //靜態創建線程 -(void)staticCreateThread{ [NSThread detachNewThreadSelector:@selector(loadImageSource:) toTarget:self withObject:imgUrl]; } //隱式創建線程 -(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)看先效果圖
NSThread多線程加載效果
1.4)NSThread的拓展認識
①獲取當前線程
NSThread *current = [NSThread currentThread];
②獲取主線程
NSThread *main = [NSThread mainThread];
③暫停當前線程
[NSThread sleepForTimeInterval:2];
④線程之間通信
//在指定線程上執行操作 [self performSelector:@selector(run) onThread:thread withObject:nil waitUntilDone:YES]; //在主線程上執行操作 [self performSelectorOnMainThread:@selector(run) withObject:nil waitUntilDone:YES]; //在當前線程執行操作 [self performSelector:@selector(run) withObject:nil];
顯然動態創建線程多了幾行代碼,其實就是那幾行代碼,如果重復編寫數遍那是一件多麼不爽的事情。首次看來靜態方法創作線程和隱式創建線程顯得比較方便,簡潔。從知識結構來說,講到這裡應該講述一下線程鎖,鑒於並不常用和文章過長就不在此詳細講述,有興趣可以自行查閱。
2. NSOperation
主要的實現方式:結合NSOperation和NSOperationQueue實現多線程編程。
實例化NSOperation的子類,綁定執行的操作。
創建NSOperationQueue隊列,將NSOperation實例添加進來。
系統會自動將NSOperationQueue隊列中檢測取出和執行NSOperation的操作。
2.1)使用NSOperation的子類實現創作線程。
①.NSInvocationOperation創建線程。
NSInvocationOperation *invocationOperation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(loadImageSource:) object:imgUrl]; //[invocationOperation start];//直接會在當前線程主線程執行 NSOperationQueue *queue = [[NSOperationQueue alloc]init]; [queue addOperation:invocationOperation];
②.NSBlockOperation創建線程
NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{ [self loadImageSource:imgUrl]; }]; NSOperationQueue *queue = [[NSOperationQueue alloc]init]; [queue addOperation:blockOperation];
③.自定義NSOperation子類實現main方法
實現main方法
-(void)main { // Do somthing }
創建線程實例並添加到隊列中
LoadImageOperation *imageOperation = [LoadImageOperation new]; imageOperation.loadDelegate = self; imageOperation.imgUrl = imgUrl; NSOperationQueue *queue = [[NSOperationQueue alloc]init]; [queue addOperation:imageOperation];
2.2)使用這三種方式編寫代碼
創建各個實例並添加到隊列表當中
//使用子類NSInvocationOperation -(void)useInvocationOperation{ NSInvocationOperation *invocationOperation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(loadImageSource:) object:imgUrl]; //[invocationOperation start];//直接會在當前線程主線程執行 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主要代碼實現
- (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)看先效果圖
NSOperation多線程加載效果
3. GCD多線程
GCD是Apple開發,據說高性能的多線程解決方案。既然這樣,就細說一下這個解決方案。
進過Nsthread和NSOperation的講述和上邊的基礎概念,可以開始組合用起來吧。並發隊列、串行隊列都用起來。
3.1)分發隊列種類(dispatch queue)
①.UI主線程隊列 main queue
dispatch_get_main_queue()
②.並行隊列global dispatch queue
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
這裡的兩個參數得說明一下:第一個參數用於指定優先級,分別使用DISPATCH_QUEUE_PRIORITY_HIGH和DISPATCH_QUEUE_PRIORITY_LOW兩個常量來獲取高和低優先級的兩個queue;第二個參數目前未使用到,默認0即可
③.串行隊列serial queues
dispatch_queue_create("minggo.app.com", NULL);
3.2)6中多線程實現
①後台執行線程創建
dispatch_async(dispatch_get_global_queue(0, 0), ^{ [self loadImageSource:imgUrl1]; });
②UI線程執行(只是為了測試,長時間加載內容不放在主線程)
dispatch_async(dispatch_get_main_queue(), ^{ [self loadImageSource:imgUrl1]; });
③一次性執行(常用來寫單例)
static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ [self loadImageSource:imgUrl1]; });
④並發地執行循環迭代
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(@"循環執行第%li次",i); [self loadImageSource:imgUrl1]; });
⑤延遲執行
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)對比多任務執行
異步加載圖片是大部分app都要問題,那麼加載圖片是按循序加載完之後才刷新UI呢?還是不安順序加載UI呢?顯然大部分的希望各自加載各自的圖片,各自刷新。以下就是模擬這兩種場景。
①先後執行,加載兩張圖片為例
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; }); });
②並行隊列執行,也是以加載兩張圖片為例
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即可。從加載圖片中來說,第①種不太合適使用,但是對於在上邊場景選擇的時候的創作工具來說有很大的好處,首先得異步進行,然後異步中有得按順序執行幾個任務,比如上傳章節內容。因此,我們可以靈活考慮使用這兩多線程任務執行方式,實現各種場景。
3.4)編碼實現
以上3.3的內容99%代碼一樣,就不提供一個稍微整體的代碼了。看看下邊的效果圖吧。
3.5)效果圖如下
GCD多線程加載效果
五.源碼地址
https://github.com/minggo620/iOSMutipleThread.git
如果小明這麼跟銀行櫃台的MM講多線程,會不會。。。“給我滾出去~~”。