iOS多线程NSThread,NSOperation和GCD详解

Posted HelloWord杰少

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了iOS多线程NSThread,NSOperation和GCD详解相关的知识,希望对你有一定的参考价值。

线程是特别有用的,当你需要执行一个特别耗时的任务,但又不希望它阻塞程序的其余部分功能的执行。特别是,你可以使用线程来避免阻塞应用程序的主线程去处理用户界面的事件和有关的行动的功能。线程还可以用于将大型的工作划分成几个较小的部分,从而去提高设备的性能。


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 = @"https://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详解的主要内容,如果未能解决你的问题,请参考以下文章

iOS开发多线程--(NSOperation/Queue)

16iOS多线程篇:NSThread

iOS —— 多线程NSThread

UI-NSOperation线程

iOS多线程篇:NSThread

iOS 多线程NSThread理解与场景示例