線程是特別有用的,當你需要執行一個特別耗時的任務,但又不希望它阻塞程序的其余部分功能的執行。特別是,你可以使用線程來避免阻塞應用程序的主線程去處理用戶界面的事件和有關的行動的功能。線程還可以用於將大型的工作劃分成幾個較小的部分,從而去提高設備的性能。
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 是蘋果公司對 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中已不再支持。
運行效果如下:
vc/C1NguPC9wPgoKPHByZSBjbGFzcz0="brush:java;">#import
#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];
}
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多線程的操作,就講到這裡為止吧!後續如果有新的內容我會定期更新上去。
歡迎大家關注我的微信公眾號,有什麼問題可以隨時聯系,掃描下方二維碼添加: