標籤:
線程是特別有用的,當你需要執行一個特別耗時的任務,但又不希望它阻塞程式的其餘部分功能的執行。特別是,你可以使用線程來避免阻塞應用程式的主線程去處理使用者介面的事件和有關的行動的功能。線程還可以用於將大型的工作劃分成幾個較小的部分,從而去提高裝置的效能。
NSThread
NSThread是相對輕量級的多線程開發範式,但使用起來也是相對複雜,我們需要自己去管理線程的生命週期,線程之間的同步。
在iOS開發中我們可以用以下三種形式來實現NSThread:
a.動態執行個體化
//動態建立NSThread- (void)dymaticCreateThread:(NSString *)url{ NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(downLoadImage:) object:url]; [thread start];}
b.靜態執行個體化
//靜態建立NSThread- (void)staticCreateThread:(NSString *)url{ [NSThread detachNewThreadSelector:@selector(downLoadImage:) toTarget:self withObject:url];}
c.隱式執行個體化
//在後台建立NSThread- (void)bkCreatThread:(NSString *)url{ [self performSelectorInBackground:@selector(downLoadImage:) withObject:url];}
- (void)downLoadImage:(NSString *)imageUrl{ NSURL *url = [NSURL URLWithString:imageUrl]; NSData *data = [NSData dataWithContentsOfURL:url]; //在主線程中更新介面 [self performSelectorOnMainThread:@selector(loadTheImage:) withObject:data waitUntilDone:YES];}- (void)loadTheImage:(NSData *)mdata{ self.imageBlock(mdata);}
運行效果如下:
當點擊了按鈕以後會啟動一個新的線程,進行圖片的下載,在這期間並不會去阻塞主線程的執行。當圖片下載完成以後,會調用UI線程將圖片顯示到介面上去。
NSOperation&NSOperationQueue
NSOperation 是蘋果公司對 GCD 的封裝,NSOperation 只是一個抽象類別,不能用於封裝任務, 所以需要用它的子類NSInvocationOperation 和 NSBlockOperation來封裝,兩種方式沒有本質的區別,但是後者使用Block的形式進行程式碼群組織,在使用的過程中更加方便。
可以看到 NSOperation 和 NSOperationQueue 分別對應 GCD 的 任務 和 隊列 。
操作步驟也很好理解:
1.將要執行的任務封裝到一個 NSOperation 對象中。
2.將此任務添加到一個 NSOperationQueue 對列中,線程就會依次啟動。
範例程式碼如下:
/* *NSInvocationOperation 方式 */- (void)NSInvocationOperationTest:(NSString *)url{ NSInvocationOperation *invaocationOperation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(loadImage:) object:url]; NSOperationQueue *operationqueue = [[NSOperationQueue alloc] init]; [operationqueue addOperation:invaocationOperation];}/* *NSBlockOperation 方式 */- (void)NSBlockOperationTest:(NSString *)url{ NSOperationQueue *operationQueue = [[NSOperationQueue alloc] init];// //建立操作塊添加到隊列// NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{// [self loadImage:url];// }];// [operationQueue addOperation:blockOperation]; //直接使用操作隊列添加操作 [operationQueue addOperationWithBlock:^{ [self loadImage:url]; }];}
相比NSInvocationOperation推薦使用NSBlockOperation,因為它代碼簡單,同時由於閉包性使它沒有傳參問題,NSInvocationOperation在SWIFT中已不再支援。
運行效果如下:
自訂繼承NSOperation,強調內容實現-(void)main函數,新開一個線程,實現圖片非同步下載.
#import <Foundation/Foundation.h>#import <UIKit/UIKit.h>@class LoadImageOperation;@protocol LoadImageDelegate <NSObject>- (void)downloadOperation:(LoadImageOperation *)operation didFinishDownLoad:(NSData *)image;@end@interface LoadImageOperation : NSOperation@property (strong, nonatomic) NSString *imgUrl;@property (strong, nonatomic) id<LoadImageDelegate> delegate;@end
#import "LoadImageOperation.h"@implementation LoadImageOperation@synthesize imgUrl = _imgUrl;@synthesize delegate = _delegate;- (void)main{ NSURL *url = [NSURL URLWithString:self.imgUrl]; NSData *data = [NSData dataWithContentsOfURL:url]; if([self.delegate respondsToSelector:@selector(downloadOperation:didFinishDownLoad:)]){ dispatch_async(dispatch_get_main_queue(), ^{ [self.delegate downloadOperation:self didFinishDownLoad:data]; }); }}@end
以下代碼為調用方式:
- (IBAction)buttomClick:(id)sender { self.imageView.image = nil; LoadImageOperation *loadtest = [[LoadImageOperation alloc] init]; loadtest.imgUrl = @"http://img.blog.csdn.net/20160622181936607"; loadtest.delegate = self; NSOperationQueue *operationqueue = [[NSOperationQueue alloc] init]; [operationqueue addOperation:loadtest];}
運行效果如下:
NSOperation依賴
當NSOperation對象需要依賴於其它NSOperation對象完成時再操作,就可以通過addDependency方法添加一個或者多個依賴的對象,只有所有依賴的對象都已經完成操作後,最開始的NSOperation對象才會開始執行,通過removeDependency來刪除依賴對象。使用NSOperation進行多線程開發還可以設定最大並發線程,有效對線程進行控制。
- (void)depandenceTest:(NSArray *)urlArray{ int count = 5; //建立操作隊列 NSOperationQueue *operationQueue = [[NSOperationQueue alloc] init]; //設定最大並發線程數量 operationQueue.maxConcurrentOperationCount = 5; NSBlockOperation *lastBlockOperation = [NSBlockOperation blockOperationWithBlock:^{ [self loadImage:[urlArray objectAtIndex:count - 1]]; }]; for(int i = 0; i < count - 1; i++){ NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{ [self loadImage:[urlArray objectAtIndex:i]]; }]; //設定依賴操作 [blockOperation addDependency:lastBlockOperation]; [operationQueue addOperation:blockOperation]; } //將最後的操作加入線程隊列 [operationQueue addOperation:lastBlockOperation];}
GCD
Grand Central Dispatch (GCD),它是為蘋果多核的並行運算提出的解決方案,所以會自動合理的利用更多的CPU核心,更重要的是它會自動的管理線程的生命週期(建立線程,調度任務,銷毀線程)。
在開始使用GCD的時候,需要搞清楚任務和隊列這兩個概念。
任務 有兩種執行方式:
1.同步操作(sync),它會阻塞當前線程的操作並等待Block中的任務執行完畢,然後當前線程才會繼續往下執行。
2.非同步作業(async),當前線程會直接的往下執行,不會阻塞當前的線程。?
隊列 也有兩種隊列,串列隊列與並行隊列
串列隊列:遵照先進先出的原則,取出來一個執行一個,建立串列隊列時可用函數dispatch_queue_create來建立,其中第一個參數是標識符,第二個參數用於表示建立的隊列是串列還是並行的,傳入 DISPATCH_QUEUE_SERIAL 或 NULL 表示建立串列隊列。
代碼如下:
//建立一個串列隊列 //第一個參數 建立隊列名稱,方便Debug //第二個參數 對列類型 DISPATCH_QUEUE_SERIAL(串列)DISPATCH_QUEUE_CONCURRENT(並發,實際上不使用該方式建立) dispatch_queue_t serialQueue = dispatch_queue_create("myThread", DISPATCH_QUEUE_SERIAL); dispatch_async(serialQueue, ^{ [self loadImage:url]; });
並行隊列:也會遵照先進先出的原則,但不同的是它會將取出來的任務放到別的線程執行,然後再取出來一個放到另一個線程。建立並發隊列時也可用函數dispatch_queue_create來建立,傳入 DISPATCH_QUEUE_CONCURRENT 表示建立並行隊列。但是在實際開發中我們會通過dispatch_get_global_queue()方法取得一個全域的並發隊列,系統為每一個應用提供了3個並發隊列,而且都是全域的,只是每個隊列它們的優先順序不同,分別是:
define DISPATCH_QUEUE_PRIORITY_HIGH 2 優先順序最高
define DISPATCH_QUEUE_PRIORITY_DEFAULT 0 優先順序中等
define DISPATCH_QUEUE_PRIORITY_LOW (-2) 優先順序最低
//建立一個並行隊列 //線程優先順序 //傳0 dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); dispatch_async(globalQueue, ^{ [self loadImage:url]; });
- (void)loadImage:(NSString *)url{ NSURL *mUrl = [NSURL URLWithString:url]; NSData *data = [NSData dataWithContentsOfURL:mUrl]; dispatch_async(dispatch_get_main_queue(), ^{ self.block(data); });}
運行效果如下:
在GCD中還有一個特殊的隊列———主隊列,用來執行主線程上的操作,dispatch_get_main_queue() 它是全域可用的串列隊列.
另外GCD還有其他任務執行方法:
dispatch_group_async(隊列組)的使用,隊列組可以將很多隊列添加到一個組裡,這樣做的好處是,當這個組裡所有的任務都執行完了,隊列組會通過dispatch_group_notify()方法獲得完成通知。
dispatch_barrier_async():寫入操作會確保隊列前面的操作執行完畢才開始,並會阻塞隊列中後來的操作.直到它執行完成後才會執行。
dispatch_apply():重複執行某個任務。
dispatch_once():單次執行一個任務,此方法中的任務只會執行一次,重複調用也沒辦法重複執行,單例模式中常用此方法。
dispatch_time():延遲一定的時間後執行。
可能上面的運行效果大家體會不到用多線程實現圖片非同步載入的效果,接下來我會在視圖中加入6個UIImageView,分別開啟6個線程來給UIImageView載入圖片。
運行效果如下:
範例程式碼如下:
#import "ShowExampleViewController.h"@interface ShowExampleViewController ()@end@implementation ShowExampleViewController@synthesize imagesArray = _imagesArray;@synthesize urlArrays = _urlArrays;- (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view from its nib. self.urlArrays = [[NSMutableArray alloc] initWithObjects:@"http://img1.gtimg.com/14/1492/149249/14924912_980x1200_0.jpg", @"http://img1.gtimg.com/14/1492/149249/14924914_980x1200_0.jpg", @"http://img1.gtimg.com/14/1492/149249/14924916_980x1200_0.jpg", @"http://img1.gtimg.com/14/1492/149249/14924917_980x1200_0.jpg", @"http://img1.gtimg.com/14/1492/149249/14924918_980x1200_0.jpg", @"http://img1.gtimg.com/14/1492/149249/14924919_980x1200_0.jpg",nil]; [NSOperationTest getInstance].delegate = self; [self layoutUI];}- (void)didReceiveMemoryWarning { [super didReceiveMemoryWarning]; // Dispose of any resources that can be recreated.}- (void)layoutUI{ self.imagesArray = [[NSMutableArray alloc] init]; for (int r=0; r<2; r++) { for (int c=0; c<3; c++) { UIImageView *imageView=[[UIImageView alloc]initWithFrame:CGRectMake(c*100 + ( c*10 ), r*100 + (r*10) + 60, 100, 100)]; imageView.contentMode=UIViewContentModeScaleAspectFit; [self.view addSubview:imageView]; [_imagesArray addObject:imageView]; } }}-(void)updateImage:(NSData *)imageData Index:(int)index{ UIImage *image=[UIImage imageWithData:imageData]; UIImageView *imageView = _imagesArray[index]; imageView.image = image;}- (IBAction)clickLoadImage:(id)sender { for(int i = 0; i < [self.urlArrays count]; i++){ [[NSOperationTest getInstance] NSBlockOperationTest:[self.urlArrays objectAtIndex:i] Index:i]; }}- (void)downloadOperation:(LoadImageOperation *)operation didFinishDownLoad:(NSData *)image{}- (void)downloadOperation:(LoadImageOperation *)operation didFinishDownLoad:(NSData *)image Index:(int)index{ [self updateImage:image Index:index];}@end
有關iOS多線程的操作,就講到這裡為止吧!後續如果有新的內容我會定期更新上去。
歡迎大家關注我的公眾號,有什麼問題可以隨時聯絡,掃描下方二維碼添加:
iOS多線程NSThread,NSOperation和GCD詳解