iOS多线程之GCD详解

时间:2022-09-05 05:21:31

GCD(Grand Central Dispatch)是基于C语言开发的一套多线程开发机制。也是目前苹果官方推荐的多线程开发方法。iOS三种多线程开发中GCD是抽象层次最高的。当然用起来也是最简单的。只是它基于C语言开发。并不像NSOperation是面向对象的开发。而是完全面向过程的。这种机制相比较于前面两种多线程开发方式最明显的优点就是它对于多核运算更佳有效。

GCD中也有一个类似于NSoperationQueue的队列,GCD统一管理整个队列中的任务。但是GCD中的队列氛围并行队列和串行队列两类。

  串行队列:只有一个线程,加入到队列中的操作按添加顺序依次执行。

  并发队列:有多个线程,操作进来之后他会将这些队列安排到可用的处理器上。同时保证先进来的任务优先处理。

其实在GCD中还有一个特殊的队列就是主队列,用来执行主线程上的操作任务。(从前面的演示中可以看到其实在NSOperation中也有一个主队列)

串行队列

使用串行队列时首先要创立一个串行队列,然后调用异步调用方法,在此方法中传入串行队列和线程操作即可自动执行。下面就是一个例子。

#define ROW_COUNT 5
#define COLUMN_COUNT 3
#define ROW_HEIGHT 100
#define ROW_WIDTH ROW_HEIGHT
#define CELL_SPACING 10

@interface ViewController () {
    NSMutableArray *_imageViews;
    NSMutableArray *_imageNames;
}

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    
    [self layoutUI];
}


- (void)layoutUI {
    _imageViews = [NSMutableArray array];
    for (int r=0; r<ROW_COUNT; r++) {
        for (int c=0; c<COLUMN_COUNT; c++) {
            UIImageView *imageView=[[UIImageView alloc]initWithFrame:CGRectMake(c*ROW_WIDTH+(c*CELL_SPACING), r*ROW_HEIGHT+(r*CELL_SPACING                           ), ROW_WIDTH, ROW_HEIGHT)];
            imageView.contentMode=UIViewContentModeScaleAspectFit;
            //            imageView.backgroundColor=[UIColor redColor];
            [self.view addSubview:imageView];
            [_imageViews addObject:imageView];
            
        }
    }
    
    UIButton *button=[UIButton buttonWithType:UIButtonTypeRoundedRect];
    button.frame=CGRectMake(50, 500, 220, 25);
    [button setTitle:@"加载图片" forState:UIControlStateNormal];
    //添加方法
    [button addTarget:self action:@selector(loadImageWithMultiThread) forControlEvents:UIControlEventTouchUpInside];
    [self.view addSubview:button];
    
    //创建图片链接
    _imageNames=[NSMutableArray array];
    for (int i=0; i<ROW_COUNT*COLUMN_COUNT; i++) {
        [_imageNames addObject:[NSString stringWithFormat:@"http://images.cnblogs.com/cnblogs_com/kenshincui/613474/o_%i.jpg",i]];
    }
    
}


- (void)loadImageWithMultiThread {
    int count = ROW_COUNT * COLUMN_COUNT;
    //创建一个串行队列
    /*
     第一个参数是 队列名称
     第二个参数是 队列类型
     */
    //注意 dispatch_queue_t的对象不是指针类型
    dispatch_queue_t serialQueue = dispatch_queue_create("mySeriQueuw", DISPATCH_QUEUE_SERIAL);
    //创建多个线程用于填充图片
    for (int i = 0; i < count; i++) {
        //异步执行队列任务
        dispatch_async(serialQueue, ^{
            [self loadImage:[NSNumber numberWithInt:i]];
        });
    }
    
}

- (void)loadImage:(NSNumber *)index {
    //如果在串行队列中会发现当前的线程打印完全一样 因为她们在一个线程中
    NSLog(@"thread is: %@",[NSThread currentThread]);
    int i = (int)[index integerValue];
    //请求数据
    NSData *data = [self requestData:i];
    //回到主线程更新UI
    dispatch_sync(dispatch_get_main_queue(), ^{
        [self updateImageWithData:data andIndex:i];
    });
    
}

- (void)updateImageWithData:(NSData *)data andIndex:(int) index{
    UIImage *image=[UIImage imageWithData:data];
    UIImageView *imageView= _imageViews[index];
    imageView.image=image;
}

- (NSData *)requestData:(int)index {
    NSURL *url=[NSURL URLWithString:_imageNames[index]];
    NSData *data=[NSData dataWithContentsOfURL:url];
    
    return data;
}

在上面的代码中更新UI还使用了GCD方法的主线程队列dispatch_get_main_queue(),其实这与前面两种主线程更新UI没有本质的区别.

并发队列

并发队列同样是使用dispatch_queue_create()方法创建,只是最后一个参数指定为DISPATCH_QUEUE_CONCURRENT进行创建,但是在实际开发中我们通常不会重新创建一个并发队列而是使用dispatch_get_global_queue()方法取得一个全局的并发队列(当然如果有多个并发队列可以使用前者创建)。下面通过并行队列演示一下多个图片的加载。代码与上面串行队列加载类似,只需要修改照片加载方法如下:

-(void)loadImageWithMultiThread{
    int count=ROW_COUNT*COLUMN_COUNT;
    
    /*取得全局队列
     第一个参数:线程优先级
     第二个参数:标记参数,目前没有用,一般传入0
    */
    dispatch_queue_t globalQueue=dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    //创建多个线程用于填充图片
    for (int i=0; i<count; ++i) {
        //异步执行队列任务
        dispatch_async(globalQueue, ^{
            [self loadImage:[NSNumber numberWithInt:i]];
        });
    }
}

GCD中执行任务有异步执行dispatch_async()和同步执行dispathc_sync()。

在GCD中一个操作是多线程执行还是单线程执行取决于当前队列类型和执行方法。只有队列类型为并列类型并且使用异步调用方法执行时才能在多个线程中执行。

串行队列可以按顺序执行 并列队列的一步方法无法确定执行顺序

UI的更新最好采用同步方法,其他操作才用异步方法

GCD中多线程操作方法不需要使用@autoreleasepool,GCD会管理内存。

 

其他任务执行方法

1.dispatch_apply()重复执行某个任务,但是注意这个方法没有异步执行(为了不阻塞线程可以使用dispatch_async()包装一下)

2.dispatch_once()单次执行一个任务,此方法中的任务只会执行一次,重复调用也没办法重复执行 常用在单利模式中

3.dispatch_time()延时一定的时间后执行

4.dispatch_barrier_async() 使用此方法创建的任务 首先会查看队列中有没有别的任务要执行,如果有,则会等待已有任务执行完毕再执行;同时在此方法后添加的任务必须等待此方法中任务执行后才能执行。(利用这个方法可以控制执行顺序,例如前面先加载最后一张图片的需求就可以先使用这个方法将最后一张图片加载的操作添加到队列,然后调用dispatch_async()添加其他图片加载任务) 

5.dispatch_group_async():实现对任务分组管理,如果一组任务全部完成可以通过dispatch_group_notify()方法获得完成通知(需要定义dispatch_group_t作为分组标识)。