并发 (二):Grand Central Dispatch

向Grand Central Dispatch(大型中枢调度)派发任务

队列有三种:
1. main thread
2. serial thread
3. concurrent thread

有两种方法向dispatch queue(调度队列)提交任务:
- Block对象
- C 函数

之前讲过Block对象,现在来说说C函数。

必需提供给GCD的C函数需要是despatch_function_t类型。它在Apple libraries里边是这样被定义的:

typedef void (*dispatch_function_t)(void*)

使用GCD执行UI相关任务

解决方案:
使用dispatch_get_main_queue方法

讨论:
UI相关任务是执行在主线程上的,所以main queue是唯一选择。我们有两种方式来分发到主队列:

  1. dispatch_async
    执行一个block对象到调度队列
  2. dispatch_async_f
    执行一个C函数到调度队列

以上两种方法都是异步方法,dispatch_sync方法不能在主线程上使用,因为它会阻塞进程,导致App进入死锁。所有通过GCD提交到主队列的任务都应该是异步提交的。

dispatch_async

它有两个参数:

  1. 调度队列句柄(dispatch queue handle)
  2. block对象

来一发无参数无返回值的小例子:

使用Block
 dispatch_queue_t mainQueue = dispatch_get_main_queue();
    dispatch_async(mainQueue, ^{
        UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"GCD" message:@"GCD is amazing!" preferredStyle:UIAlertControllerStyleAlert];

        UIAlertAction *okAction = [UIAlertAction actionWithTitle:@"好的" style:UIAlertActionStyleDefault handler:nil];
        [alertController addAction:okAction];

        [self showViewController:alertController sender:nil];
    });
使用C函数,我们可以提交一个指向application定义的上下文的指针
以下这些内容都写在AppDelegate.mtypedef struct{
    char *title;
    char *message;
    char *cancelButtonTitle;
}AlertViewData;

void displayAlertView(void *paramContext){
    AlertViewData *alertData = (AlertViewData *)paramContext;
    NSString *title = [NSString stringWithUTF8String:alertData->title];
    NSString *message = [NSString stringWithUTF8String:alertData->message];
    NSString *cancelButtonTitle = [NSString stringWithUTF8String:alertData->cancelButtonTitle];

    [[[UIAlertView alloc]initWithTitle:title message:message delegate:nil cancelButtonTitle:cancelButtonTitle otherButtonTitles:nil, nil]show];
    free(alertData);
}
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    // Override point for customization after application launch.

    dispatch_queue_t mainQueue = dispatch_get_main_queue();
    AlertViewData *context = (AlertViewData*)malloc(sizeof(AlertViewData));
    if (context) {
        context->title = "GCD";
        context->message = "GCD is amazing!";
        context->cancelButtonTitle = "OK";

        dispatch_async_f(mainQueue, (void *)context, displayAlertView);
    }
    return YES;
}

//    dispatch_async_f(<#dispatch_queue_t queue#>, <#void *context#>, <#dispatch_function_t work#>)
参数一:确认队列;参数二:提交上下文,也就是参数;参数三:调用的C语言方法都是dispatch_function_t类型
我们可以通过NSThread类查看当前队列和主队列:
NSLog(@"current thread = %@",[NSThread currentThread]);
NSLog(@"main thread = %@",[NSThread mainThread]);

使用GCD同步执行与UI无关的任务

解决方案:
使用dispatch_sync方法
讨论:
任何与UI无关的任务,都可以使用全局并发队列(globle concurrent queues),它既可以使用同步方法,也允许使用异步方法。
对于同步方法来说,它并不意味着你的应用要等着任务完成后才能继续,而是说在那个并发线程上的其他任务,需要等待这个队列上 的当前任务完成后才能继续执行。你的程序将会一直执行,因为并发队列并不执行在主队列上。(只有一个例外,当一个任务通过dispatch_sync提交到并发队列concurrent queue或者串行队列serial queue,如果可能的话,iOS将会让这个任务执行在当前队列上,而这个current queue也可能是主线程,它取决于那个时候代码路径在哪里。这个是GCD在编码时候的一个优化。)
如果你提交一个同步任务到并发队列,同时提交另一个同步任务到另一个并发队列,这两个同步任务对对方来说都是异步执行的,因为它们是跑在不同的并发队列上的。
理解以上概念是很重要的。如果你想要确保任务B必须等任务A结束后才开始,你可以将它们同步的放到同一个队列上去。
你可以使用dispatch_sync方法执行同步任务在一个调度队列上,你所需要做的只是,提供一个负责处理任务的队列,以及一个block代码来执行在这个队列上。

下面这个例子,它打印了1-1000两次,一个队列完成后接着另一个队列,并没有阻塞主线程。我们可以创建一个block对象,来为我们做计算,并且同步地调用相同的block对象两次

void (^printFrom1To1000)(void) = ^{
    NSUInteger counter = 0;
    for(counter = 1 ; counter <= 1000 ; counter++){
        NSLog(@"counter = %lu - Thread = %@",(unsigned long)counter,[NSThread currentThread]);
    }
};
//-------------------
dispatch_queue_t concurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_sync(concurrentQueue, printFrom1To1000);
    dispatch_sync(concurrentQueue, printFrom1To1000);

运行的结果是,两个队列依次执行,把第一个执行完后,再打印第二个1-1000,两个队列都执行在主线程上,即使你是使用并发队列来执行这个任务的。原来,这是GCD的优化。dispatch_sync方法将会使用当前线程(就是你用来调度任务的线程),在任何可能的时候,作为已经被编入GCD的优化的一部分。苹果官方是这样说的:
As an optimization, this function invokes the block on the current thread when possible.

C语言的调用方法是这样子的:

void printFrom1To1000 (void *paramContext){
    NSUInteger counter = 0;
    for(counter = 1 ; counter <= 1000 ; counter++){
        NSLog(@"counter = %lu - Thread = %@",(unsigned long)counter,[NSThread currentThread]);
    }
};
//-------------
dispatch_queue_t concurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_sync_f(concurrentQueue, NULL, printFrom1To1000);
    dispatch_sync_f(concurrentQueue, NULL, printFrom1To1000);
dispatch_get_global_queue(<#long identifier#>, <#unsigned long flags#>)

第一个参数是指定队列的优先级,可选项分别有:DISPATCH_QUEUE_PRIORITY_HIGH、DISPATCH_QUEUE_PRIORITY_DEFAULT、DISPATCH_QUEUE_PRIORITY_LOW
级别越高,任务就会被分配更多的CPU时间片段

第二个参数是保留值,你只需要记住永远给它赋值0就对了

使用GCD异步执行与UI无关的任务

这是GCD最精华的部分,可以同时在main、serial、concurrent上异步执行block代码。
解决方案:
1. dispatch_async
2. dispatch_async_f

场景:现在想要从网络上下载一个图片,下载完成后显示给用户。
思路:

  1. 在并发队列上异步发动一个block对象;
  2. 一旦进入到这个block中,我们就同步发动另一个block对象,使用dispatch_sync从URL上下载一个图片。我们这样做是为了让这个并发队列的其他代码等待图片下载完成。因此,我们只是令这个并发队列等待,而不是其他的队列。总的来说,我们就是在一个异步的队列中同步的下载一张图片,我们需要关注的就是下载的时候不要去阻塞主线程;
  3. 当图片下载好了之后,我们就会同步的执行一个block对象到主队列上,是为了去刷新UI展示图片。
思路:
dispatch_queue_t concurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_async(concurrentQueue, ^{
        __block UIImage *image = nil;
        dispatch_sync(concurrentQueue, ^{
            /*Down load the image here*/
        });
        dispatch_sync(dispatch_get_main_queue(), ^{
            /*Show the image to the user here on the main queque*/
        });
    });
再具体一点

    dispatch_queue_t concurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_async(concurrentQueue, ^{
        __block UIImage *image = nil;
        dispatch_sync(concurrentQueue, ^{
            NSString *urlAsString = @"http://image2.sina.com.cn/ent"\
            "/2004-12-18/1103339247_UQaqtq.jpg";
            NSURL *url = [NSURL URLWithString:urlAsString];
            NSURLRequest *urlRequest = [NSURLRequest requestWithURL:url];
            NSError *downloadError = nil;

            NSData *imageData = [NSURLConnection sendSynchronousRequest:urlRequest
                                                      returningResponse:nil
                                                                  error:&downloadError];
            if (!downloadError && imageData) {
                imageData = [UIImage imageWithData:imageData];
            }else if(downloadError){
                NSLog(@"Error happened = %@",downloadError);
            }else{
                NSLog(@"No data could get downloaded from the URL");
            }

        });
        dispatch_sync(dispatch_get_main_queue(), ^{
            if (image) {
                /* Show the image */
            }else{
                NSLog(@"Image isn't downloaded. Nothing to display");
            }
        });
    });

使用GCD延后执行任务

解决方案:

  1. dispatch_after
  2. dispatch_after_f

dispatch_after(<#dispatch_time_t when#>, <#dispatch_queue_t queue#>, <#^(void)block#>)
参数:纳秒单位的延迟时间,调度的队列,Block对象;

dispatch_after_f(<#dispatch_time_t when#>, <#dispatch_queue_t queue#>, <#void *context#>, <#dispatch_function_t work#>)
参数:纳秒单位的延迟时间,调度的队列,给C方法使用的上下文,C方法

注意:虽然延迟的单位是纳秒,但是它还是由iOS来觉得派遣延迟的粒度大小,因此,延迟的时间也许不会像设定的那么精确。

代码示例:

double delayInSeconds = 2.0;
dispatch_time_t delayInNanoSeconds = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);
dispatch_queue_t concurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_after(delayInNanoSeconds, concurrentQueue, ^{
    /* Perform your operations here */
});

dispatch_time_t类型是一个绝对时间的抽象呈现。为了得到这个值,你可以使用上面代码片段中的dispatch_time方法。以下参数是你可以传递给dispatch_time方法的:
(Base time,Delta to add to base time)
base time可以设定为DISPATCH_TIME_NOW。无论最后B或者D设置为什么值,最终dispatch_time方法的时间总和就是B+D。
B表示从某一时刻开始的时间,D就是从B开始要延迟的那个时间。
举例来说:

从现在开始的3s后
dispatch_time_t delayInNanoSeconds = dispatch_time(DISPATCH_TIME_NOW, 3.f * NSEC_PER_SEC);
或者从现在开始的半秒后
dispatch_time_t delayInNanoSeconds = dispatch_time(DISPATCH_TIME_NOW, (1/2.f) * NSEC_PER_SEC);

C语言的实现方式:略,不想写了

用GCD只执行一次任务

解决方法:
dispatch_once

dispatch_once(<#dispatch_once_t *predicate#>, <#^(void)block#>)
参数:
Token:dispatch_once_t类型的token可以保留第一次从GCD中产生的token;
block 对象;

代码:

static dispatch_once_t onceToken;
void (^executedOnlyOnce)(void) = ^{
    static NSUInteger numberOfEntries = 0;
    numberOfEntries ++;
    NSLog(@"Excuted %lu time(s)",(unsigned long)numberOfEntries);
};
//--------------------------------

dispatch_queue_t concurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

    dispatch_once(&onceToken, ^{
        dispatch_async(concurrentQueue, executedOnlyOnce);
    });

    dispatch_once(&onceToken, ^{
        dispatch_async(concurrentQueue, executedOnlyOnce);
    });

执行的结果是:

Excuted 1 time(s)

实例:使用dispatch_once制作singleton。

-(id)sharedInstance{
    static MySingleton *sharedInstance = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        sharedInstance = [MySingleton new];
    });
    return sharedInstance;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值