iOS7 Programming Cookbook-Chapter 7:Concurrency(GCD部分)

本文是笔者自己自学iOS7开发的过程中,练习翻译的《iOS7 Programming Cookbook》,兼做个人的笔记和代码汇总。

由于第七章篇幅过长,拆分成四部分,这是其中的GCD部分。

7.4 Performing UI-Related Tasks with GCD

问题:你正在使用GCD完成线程同步,你希望知道协同UI相关API工作的最佳方式。

解决方法:

使用dispatch_get_main函数

讨论:

UI相关的任务必须在主线程上执行,所以主队列是在GCD中执行UI任务的唯一候选。我们可以用dispatch_get_main_queue函数取得主分配队列的操作。

有两种将任务分配给主队列的方法。他们都是异步的,即使任务没有执行,程序依然会运行:

dispatch_async函数

 在分配队列中执行block对象。

dispatch_async_f函数

 在分配队列中执行C函数。

让我们来看看如何使用dispatch_async函数。它接收两个参数:

Dispatch queue handle

 执行任务的那个队列。

Block objects

 要被发送到分配队列上异步执行的block对象。

下面这个例子会在iOS上使用主队列显示一个警告给用户:

dispatch_queue_t mainQueue = dispatch_get_main_queue();
    dispatch_async(mainQueue, ^(void){
    
        [[[UIAlertView alloc]initWithTitle:@"GCD"
                                   message:@"GCD is amazing"
                                  delegate:nil
                         cancelButtonTitle:@"OK"
                         otherButtonTitles:nil, nil] show];
    
    });

这可能看起来印象不那么深刻。那么是什么使主队列变得有趣呢?答案很简单:当你使用GCD执行一些复杂运算时,你希望展示一些结果。比如,你必须使用主队列执行UI相关任务。这部分展示的函数是跳出队列的同时还能继续使用GCD更新UI的唯一方法,那么你就可以想象他们有多重要了。

代替block对象,你可以提交一个C方法对象给主队列执行。提交所有UI相关的C函数给GCD使用dispatch_async_f方法执行。稍加更改,我们可以得到与前例相同的结果。

像上面提到的那样,使用dispatch_async_f函数,我们可以提交一个指向应用定义的内容的指针,可以被C方法调用。所以让我们来创建一个保存alert视图的标题,内容和取消按钮标题的结构体。当应用启动后,我们把所有的值放入结构体,然后传给C函数显示,下面是我们定义的结构体:

typedef struct{ 
char *title;
char *message;
char *cancelButtonTitle; 
} AlertViewData;
现在让我们实现一个C函数,这个函数将在之后通过GCD来调用。这个C方法需要一个指针参数,我们在之后将强制转化为AlertViewData类型。换句话说,我们希望方法的调用者传给我们,封装alert视图需要的数据的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);
}
现在让我们在主线程调用displayAlertView函数,并且把内容传给它:

- (BOOL) application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions{
    dispatch_queue_t mainQueue = dispatch_get_main_queue();
    AlertViewData *context = (AlertViewData *)
    malloc(sizeof(AlertViewData));
    if (context != NULL){
        context->title = "GCD";
        context->message = "GCD is amazing.";
        context->cancelButtonTitle = "OK";
        
        dispatch_async_f(mainQueue,
                         (void *)context,
                         displayAlertView);
        
        self.window = [[UIWindow alloc] initWithFrame:
                       [[UIScreen mainScreen] bounds]];
        self.window.backgroundColor = [UIColor whiteColor]; [self.window makeKeyAndVisible];
        return YES;
    
    }
}
如果你是用NSThread的currentThread方法,你会发现你分配给主队列的运行在主线程上:
- (BOOL) application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions{
    dispatch_queue_t mainQueue = dispatch_get_main_queue();
    
        dispatch_async(mainQueue,
                       ^(void){
                           NSLog(@"Current thread = %@",[NSThread currentThread]);
                           NSLog(@"Main thread = %@", [NSThread mainThread]);
                       });
        
        self.window = [[UIWindow alloc] initWithFrame:
                       [[UIScreen mainScreen] bounds]];
        self.window.backgroundColor = [UIColor whiteColor];
        [self.window makeKeyAndVisible];
        return YES;
    
    
}
输出的结果如下:

Currentthread= <NSThread:0x4b0e4e0>{name=(null),num=1}

Mainthread= <NSThread:0x4b0e4e0>{name=(null),num=1

现在你已经知道如何使用GCD执行UI相关任务,现在我们需要转向其他主题了,比如使用同步队列并行执行任务,如果需要的话把代码和UI相关代码融合起来。

7.5 Executing Non-UI Related Tasks Synchronously with GCD

问题:你希望执行不包含UI相关代码的同步任务。

解决方法:

使用dispatch_sync函数。

讨论:

经常你希望执行与UI无关的任务,或者在与UI交互前需要做其他任务。比如你希望下载一张图片,并在下载之后展示给用户。下载过程绝对与UI无关。

对于任何不涉及UI的任务,你可以使用GCD的全局队列。允许同步或者异步的执行。但是同步执行并不意味你的程序要等待代码执行完毕后才能继续。只是你的队列会在任务完成后才继续执行队列的下一代码块。当你把block对象放在队列中,你自己的程序会立即继续执行,而不会等待队列去执行代码。因为同步队列,会在非主线程上执行代码。

如果你向一个同步队列提交一个任务,同时又向另一个队列提交另一个任务,这两个同步任务会异步执行,因为他们彼此在不同的队列上。理解这点十分重要,因为有时候你希望确保A任务在B任务开始前结束。为了保证这点,在同一队列中同步提交他们。

你可以使用dispatch_sync函数在一个调度队列中同步执行任务,所有你需要做的就是提供给它执行队列的持有者,以及队列上执行的代码段。

让我们来看一个例子,这个例子中在没有阻塞主线程的条件下两次打印了1到1000间的所有整数:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    // Override point for customization after application launch.
    dispatch_queue_t concurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_sync(concurrentQueue, printFrom1To1000);
    dispatch_sync(concurrentQueue, printFrom1To1000);
    // Override point for customization after application launch.
    [self.window makeKeyAndVisible];
    
    return YES;
}


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

如果你运行这段代码,你可能会注意到计数占用了主线程,即使你要求一个同步线程执行任务。这证明了GCD有一个优化机制。dispatch_sync函数将使用当你分配任务时使用的线程,只要在可能的时候。

为了执行C函数,让我们简单把我们刚才为printFrom1To1000的block对象所写的代码转化为等价的C函数,像这样:

void printFrom1To1000(void *paramContext){
NSUInteger counter = 0; for (counter = 1;counter <= 1000;counter++){
NSLog(@"Counter = %lu - Thread = %@", (unsigned long)counter, [NSThread currentThread]);
} 
}

现在我们使用dispatch_sync_f函数执行printFrom1To1000函数,像这样:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    // Override point for customization after application launch.
    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);
    
    self.window = [[UIWindow alloc]initWithFrame:[[UIScreen mainScreen] bounds]];
    self.window.backgroundColor = [UIColor whiteColor];
    [self.window makeKeyAndVisible];
    
    return YES;
}


void printFrom1To1000(void *paramContext){
    NSUInteger counter = 0;
    for (counter = 1;counter <= 1000;counter++){
        NSLog(@"Counter = %lu - Thread = %@", (unsigned long)counter,
              [NSThread currentThread]);
    }
}

dispatch_get_global_queue函数的第一个参数表示了队列的优先级。优先级越高,给队列中执行的代码提供的CPU时间片就越多。你可以使用下面的任一值作为dispatch_get_global_queue函数的第一个参数:

DISPATCH_QUEUE_PRIORITY_LOW

相比普通任务更少的时间片

DISPATCH_QUEUE_PRIORITY_DEFAULT

系统默认的优先级

DISPATCH_QUEUE_PRIORITY_HIGH

相比普通任务较多的时间片
这部分你看到了如何同步调度任务。下部分会展示在并行队列中如何异步调度任务。


7.6 Executing Non-UI Related Tasks Asynchronously with GCD

问题:你希望在GCD的帮助下,异步执行非UI相关任务

解决方法:

这就是GCD可以展示出他真正威力的地方了:在主队列,串行队列,并行队列中异步执行代码段。在本节结尾,你会完全相信GCD就是多线程应用的未来。

在调度队列中异步执行任务,必须要用到下面两个函数之一:

dispatch_async

dispatch_async_f

讨论

让我们来看一个实际例子,我们将编写一个从网络上下载图片的应用。在下载完成后,应用会向用户显示图片。下面是我们计划用GCD实现该功能的计划:

  1. 在并行队列上异步部署block对象。
  2. 在该block种,我们将同步部署另一block,使用dispatch_sync方法,为了下载图片。我们这样做是因为我们希望并行队列的余下代码要等到图片下载完再执行。因此我们需要并行队列等待。整个操作从主线程的角度来看仍是异步的。我们所有关心的是在下载图片时我们没有阻塞主线程。
  3. 下载完成后,我们将在主队列上同步执行一个block对象在UI上去向用户显示图片.
这个计划的骨架应该是这样的:

- (void) viewDidAppear:(BOOL)animated{
    [super viewDidAppear:animated];
    
    dispatch_queue_t concurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
    dispatch_async(concurrentQueue, ^{
        __block UIImage *image = nil;
        
        dispatch_sync(concurrentQueue, ^{
            /* Download the image here */
        });
        dispatch_sync(dispatch_get_main_queue(), ^{
            /* Show the image to the user here on the main queue */
        });
    });
}

第二个dispatch调用,显示图片,将会执行在第一个下载图片的调用后在队列中执行。

现在让我们下载图片并显示给用户。我们将在视图的viewDidAppear:实例方法中执行操作:

-(void)viewDidAppear:(BOOL)animated
{
    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://images.apple.com/mobileme/features"\
            "/images/ipad_findyouripad_20100518.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 == nil && imageData != nil){
                image = [UIImage imageWithData:imageData];
            }else if (downloadError != nil){
                NSLog(@"Error happened = %@", downloadError);
            }else{
                NSLog(@"No data could get downloaded from the URL.");
            }
            
            
        });
        
        
        dispatch_sync(dispatch_get_main_queue(), ^{
            
            if (image != nil){
                /* Create the image view here */
                UIImageView *imageView = [[UIImageView alloc]initWithFrame:self.view.bounds];
                [imageView setImage:image];
                /* Set the image */
                /* Make sure the image is not scaled incorrectly */
                [imageView setContentMode:UIViewContentModeScaleAspectFit]; /* Add the image to this view controller's view */
                [self.view addSubview:imageView];
            }else{
                NSLog(@"Image isn't downloaded. Nothing to display.");
            }
            
        });
    });

}

现在让我们转向下一个例子,假设我们有一个存储在文件中的一个长度为10000的随机数数组,我们希望将它载入内存,并且以升序排列,并展示给用户。用来展示的控制语句依赖于你是为iOS还是Mac OS X编码。既然我们没有数组,那么为什么不先创建一个再载入并展示呢?

有两个方法帮助我们找到我们希望存储一个保存1000个随机数的数组在磁盘上的位置:

- (NSString *) fileLocation{
    /* Get the document folder(s) */
    NSArray *folders =
    NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask,YES);
    /* Did we find anything? */
    if ([folders count] == 0){
        return nil;
    }
    /* Get the first folder */
    NSString *documentsFolder = folders[0];
    /* Append the filename to the end of the documents path */
    return [documentsFolder stringByAppendingPathComponent:@"list.txt"];
}

- (BOOL) hasFileAlreadyBeenCreated{
    BOOL result = NO;
    
    NSFileManager *fileManager = [[NSFileManager alloc] init];
    if ([fileManager fileExistsAtPath:[self fileLocation]]){
        result = YES;
    }
    
    return result;
}
现在重点来了:我们希望只有当我们没有在磁盘上存储过该数组的情况下再存储该存有1000个随机数的数组。如果我们已经创建过了那么我们就重新载入该数组。如果我们没有在磁盘上创建过该数组,我们就先创建再载入。最后如果数组读取成功,我们就按升序进行排列,并显示给用户。我们将把显示部分留给用户完成:

- (void) viewDidAppear:(BOOL)animated{
    [super viewDidAppear:animated];
    
    dispatch_queue_t concurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    /* If we have not already saved an array of 10,000
     random numbers to the disk before, generate these numbers now
     and then save them to the disk in an array */
    dispatch_async(concurrentQueue, ^{
        NSUInteger numberOfValuesRequired = 10000;
        if ([self hasFileAlreadyBeenCreated] == NO){
            dispatch_sync(concurrentQueue, ^{
            NSMutableArray *arrayOfRandomNumbers =
            [[NSMutableArray alloc]
             initWithCapacity:numberOfValuesRequired];
            NSUInteger counter = 0;
            for (counter = 0;counter < numberOfValuesRequired;counter++){
                unsigned int randomNumber =arc4random() % ((unsigned int)RAND_MAX + 1);
                [arrayOfRandomNumbers addObject:[NSNumber numberWithUnsignedInt:randomNumber]];
            }
            /* Now let's write the array to disk */
            [arrayOfRandomNumbers writeToFile:[self fileLocation]
                                   atomically:YES];
        });
        
    }
        __block NSMutableArray *randomNumbers = nil;
        /* Read the numbers from disk and sort them in an
         ascending fashion */
        dispatch_sync(concurrentQueue, ^{
            /* If the file has now been created, we have to read it */
            if ([self hasFileAlreadyBeenCreated]){
                randomNumbers = [[NSMutableArray alloc]initWithContentsOfFile:[self fileLocation]];
                /* Now sort the numbers */
                [randomNumbers sortUsingComparator: ^NSComparisonResult(id obj1, id obj2) {
                    NSNumber *number1 = (NSNumber *)obj1;
                    NSNumber *number2 = (NSNumber *)obj2;
                    return [number1 compare:number2];
                }]; }
        });
        dispatch_async(dispatch_get_main_queue(), ^{
            if ([randomNumbers count] > 0){
            /* Refresh the UI here using the numbers in the
             randomNumbers array */
        } });
    });
}


7.7 Performing Tasks after a Delay with GCD

问题:你希望在一个特定的延时后执行你的代码。

解决方法:

使用dispatch_after和dispatch_after_f函数。

讨论:

你可以使用NSObject类的performSelector:withObject:afterDelay:方法,下面是例子:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    
    [self performSelector:@selector(printString:) withObject:@"Grand Central Dispatch"
               afterDelay:3.0];
    self.window = [[UIWindow alloc]initWithFrame:[[UIScreen mainScreen] bounds]];
    self.window.backgroundColor = [UIColor whiteColor];
    [self.window makeKeyAndVisible];

    
    return YES;
}

- (void) printString:(NSString *)paramString{
    NSLog(@"%@", paramString);
}
在上述例子中,我们要求运行时在3秒延迟后执行printString方法。我们可以用GCD完成同样的功能,这就需要使用以下两种方法之一:

dispatch_after

在一个特定时间之后将一个block对象分配给调度队列,时间以纳秒计。下面是方法需要的参数:

  • Delay in nanoseconds:GCD需要在特定的调度队列上等待的时间,以纳秒计。
  • Dispatch queue:执行block对象的队列。
  • Block object:延迟后调度队列要执行的block对象。

dispatch_after_f

在一个特定的时间之后讲一个C函数分配给调度队列执行,该方法接收四个参数:

  • Delay in nanoseconds:需要等待的时间,以纳秒计。
  • Dispatch queue:执行C函数的队列。
  • Context:需要传给C函数的值的内存地址。
  • C function:特定时间之后执行的C函数的地址。

首先来看dispatch_after例子:

- (BOOL) application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions{
    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, ^(void){
        /* Perform your operations here */
    });
    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    // Override point for customization after application launch.
    self.window.backgroundColor = [UIColor whiteColor]; [self.window makeKeyAndVisible];
    return YES;
}

如你所见,纳秒延迟参数需要是dispatch_time_t类型的变量,这是绝对时间的一种抽象表示。为了得到该参数的值,你可以使用dispatch_time函数。下面是你需要传给dispatch_time函数的参数:

  • Base Time:如果该值记为B,delta参数记为D,该函数结果应该等于B+D。你可以用DISPATCH_TIME_NOW来标记现在作为base time。
  • Delta to add to base time:这个参数以纳秒计,将会加在base time参数上作为该函数的结果。

比如,指定一个三秒之后的延迟,你的代码应该是:

dispatch_time_t delay = dispatch_time(DISPATCH_TIME_NOW, 3.0f * NSEC_PER_SEC);

半秒之后的延迟:

dispatch_time_t delay =
dispatch_time(DISPATCH_TIME_NOW, (1.0 / 2.0f) * NSEC_PER_SEC);

现在再来看看我们如何使用dispatch_after_f函数:

- (BOOL) application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions{
    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_f(delayInNanoSeconds,concurrentQueue,NULL,processSomething);
    self.window = [[UIWindow alloc] initWithFrame:
                   [[UIScreen mainScreen] bounds]];
    // Override point for customization after application launch.
    self.window.backgroundColor = [UIColor whiteColor]; [self.window makeKeyAndVisible];
    return YES;
}


7.8 Performing a Task Only Once with GCD

问题:你希望在某段代码在应用运行期间只运行一次,无论从任何位置调用这段代码。

解决方案:使用dispatch_once函数。

讨论:

分配并初始化一个单例就是在一个应用中只能执行一次的任务之一。我确信你知道一些其他的场景,你不得不确保一段代码在你的应用的生命周期中只执行一次。

GCD允许你特别制定一个标识符来标注一段代码当你希望执行它的时候。如果GCD检测到该标识符已经在之前传给框架了,那么他就不会再执行改代码段第二次了。这个方法就是dispatch_once,它接收两个参数:

  • Token:一个dispatch_once_t类型的标识,由GCD产生。如果你希望一段代码只执行一次,你必须用同一个标识标记这个方法,无论它何时在应用中被调用。
  • Block object:只能执行一次的block对象。

下面是一个例子:

static dispatch_once_t onceToken;

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

- (BOOL) application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions{ 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);
    });
    self.window = [[UIWindow alloc] initWithFrame:
                       [[UIScreen mainScreen] bounds]];
    self.window.backgroundColor = [UIColor whiteColor]; [self.window makeKeyAndVisible];
    return YES;
}
如你所见,尽管我们我们试着调用executedOnlyOnce block两次,实际上,GCD只执行该对象一次。
Apple在Cocoa Fundamentals Guide中向程序员展示了怎样去创建一个单例。源代码尚未更新到使用GCD和ARC。我们可以把模型更改到使用GCD和dispatch_once函数的形式,像这样:
#import "MySingleton.h"
@implementation MySingleton
- (instancetype) sharedInstance{
    static MySingleton *SharedInstance = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        SharedInstance = [MySingleton new];
    });
    return SharedInstance;
}
@end


7.9 Grouping Tasks Together with GCD

问题:你希望将多段代码模块打包,并确保它们按照顺序在GCD中依次执行

解决方法:

使用dispatch_create_group方法创建一个分组。

讨论:

GCD让我们创建一个分组,允许你将你的任务都放在一个位置,一次运行全部任务,并且在最后得到GCD的提示。有许多有价值的应用,比如假设你有一个基于UI的应用,并且希望重载你的UI中的组件。你有一个table视图,scroll视图和一个image视图。你希望通过三个方法分别执行这些组件内容的重载。

现在我们希望按次序执行这三个方法,并且在执行完毕后得到一个提醒。为了这个目的,我们应该使用分组。你应该指导以下三个函数:

  • dispatch_group_create:创建一个分组句柄。
  • dispatch_group_async:提交一段代码在分组中执行。你必须指定执行这段代码的队列以及代码所属的分组。
  • dispatch_group_notify:允许你提交一个block对象,这个对象会在分组任务执行完毕后立即执行。这个函数允许你指定block对象执行的调度队列。

我们来看一个例子。这个例子像解释的那样,会依次执行组件重载方法,并在结束后发送给用户一条消息。我们可以用GCD强大的分组工具达到这个目的:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    
    dispatch_group_t taskGroup = dispatch_group_create(); dispatch_queue_t mainQueue = dispatch_get_main_queue();
    /* Reload the table view on the main queue */
    dispatch_group_async(taskGroup, mainQueue, ^{
        [self reloadTableView];
    });
    /* Reload the scroll view on the main queue */
    dispatch_group_async(taskGroup, mainQueue, ^{
        [self reloadScrollView];
    });
    /* Reload the image view on the main queue */
    dispatch_group_async(taskGroup, mainQueue, ^{
        [self reloadImageView];
    });
    /* At the end when we are done, dispatch the following block */
    dispatch_group_notify(taskGroup, mainQueue, ^{
        /* Do some processing here */
        [[[UIAlertView alloc] initWithTitle:@"Finished"
                                    message:@"All tasks are finished"
                                   delegate:nil
                          cancelButtonTitle:@"OK"
                          otherButtonTitles:nil, nil] show];
    });
    self.window = [[UIWindow alloc]initWithFrame:[[UIScreen mainScreen] bounds]];
    self.window.backgroundColor = [UIColor whiteColor];
    [self.window makeKeyAndVisible];

    
    return YES;
}
-(void) reloadTableView{
    /* Reload the table view here */ NSLog(@"%s", __FUNCTION__);
}
- (void) reloadScrollView{
    /* Do the work here */ NSLog(@"%s", __FUNCTION__);
}
- (void) reloadImageView{
    /* Reload the image view here */ NSLog(@"%s", __FUNCTION__);
}

除了dispatch_group_async方法外,你也可以使用dispatch_group_async_f方法将异步执行的C函数分配到一个调度队列里。

像这样:

void reloadAllComponents(void *context){
    AppDelegate *self = (__bridge AppDelegate *)context;
    [self reloadTableView];
    [self reloadScrollView];
    [self reloadImageView];
}
- (BOOL) application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions{
    dispatch_group_t taskGroup = dispatch_group_create();
    dispatch_queue_t mainQueue = dispatch_get_main_queue();
    dispatch_group_async_f(taskGroup,
                           mainQueue,
                           (__bridge void *)self, reloadAllComponents);
    /* At the end when we are done, dispatch the following block */
    dispatch_group_notify(taskGroup, mainQueue, ^{
        /* Do some processing here */
        [[[UIAlertView alloc] initWithTitle:@"Finished"
                                    message:@"All tasks are finished"
                                   delegate:nil
                          cancelButtonTitle:@"OK"
                          otherButtonTitles:nil, nil] show];
    });
    self.window = [[UIWindow alloc] initWithFrame:
                   [[UIScreen mainScreen] bounds]];
    self.window.backgroundColor = [UIColor whiteColor]; [self.window makeKeyAndVisible];
    return YES;
}


7.10 Constructing Your Own Dispatch Queues with GCD

问题:你希望创建你自己的调度队列

解决方法:

使用dispatch_create_queue函数。

讨论:

通过GCD,你可以创建你自己的串行调度队列。串行调度队列按照先进先出的方式运行它们的任务。串行队列的异步任务不会在主线程上执行。

所有提交给串行队列的异步任务将会在当前提交任务的代码所在的线程执行,只要情况允许。但是异步任务通常在主线程之外的线程上执行。

我们将会使用dispatch_queue_create函数创建串行队列。第一个参数是C字符串,用来在系统中唯一标识串行队列。我强调系统的原因是,这个标识符是系统范围的,这意味着如果你的应用创建了和别的应用用来创建串行队列相同的标识符,那么这个行为将不被允许。因此Apple强烈推荐你使用反DNS格式来命名标识符。反DNS标识符通常以这种方式构成:com.COMPANY.PRODUCT.IDENTIFIER。比如我会创建两个串行队列,并命名如下:

com.pixolity.GCD.serialQueue1
    com.pixolity.GCD.serialQueue2
在你创建了你的串行队列后,你可以使用你在本书中学到的GCD函数来调度任务了。让我们来看一个例子:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    
    dispatch_queue_t firstSerialQueue = dispatch_queue_create("com.pixolity.GCD.serialQueue1", 0);
    dispatch_async(firstSerialQueue, ^{
        NSUInteger counter = 0;
        for (counter = 0;counter < 5;counter++){
            NSLog(@"First iteration, counter = %lu", (unsigned long)counter);
        }
    });
    dispatch_async(firstSerialQueue, ^{
        NSUInteger counter = 0;
        for (counter = 0;counter < 5;counter++){
            NSLog(@"Second iteration, counter = %lu", (unsigned long)counter);
        }
    });
    dispatch_async(firstSerialQueue, ^{
        NSUInteger counter = 0;
        for (counter = 0;counter < 5;counter++){
            NSLog(@"Third iteration, counter = %lu", (unsigned long)counter);
        }
    });
    
    self.window = [[UIWindow alloc]initWithFrame:[[UIScreen mainScreen] bounds]];
    self.window.backgroundColor = [UIColor whiteColor];
    [self.window makeKeyAndVisible];

    return YES;
}
运行这段代码可以看到如下输出:
First iteration, counter = 0
    First iteration, counter = 1
    First iteration, counter = 2
    First iteration, counter = 3
    First iteration, counter = 4
    Second iteration, counter = 0
    Second iteration, counter = 1
    Second iteration, counter = 2
    Second iteration, counter = 3
    Second iteration, counter = 4
    Third iteration, counter = 0
    Third iteration, counter = 1
    Third iteration, counter = 2
    Third iteration, counter = 3
    Third iteration, counter = 4
很明显,尽管我们在串行队列上异步调用block对象,队列仍然以先进先出模式执行。我们可以使用dispatch_async_f函数代替dispatch_async函数,像这样:
void firstIteration(void *paramContext){
    NSUInteger counter = 0; for (counter = 0;counter < 5;counter++){
        NSLog(@"First iteration, counter = %lu", (unsigned long)counter);
    }
}

void secondIteration(void *paramContext){
    NSUInteger counter = 0;
    for (counter = 0;counter < 5;counter++){
        NSLog(@"Second iteration, counter = %lu", (unsigned long)counter);
    }
}

void thirdIteration(void *paramContext){
    NSUInteger counter = 0;
    for (counter = 0;counter < 5;counter++){
        NSLog(@"Third iteration, counter = %lu", (unsigned long)counter);
    }
}

- (BOOL) application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions{
    dispatch_queue_t firstSerialQueue = dispatch_queue_create("com.pixolity.GCD.serialQueue1", 0);
    dispatch_async_f(firstSerialQueue, NULL, firstIteration);
    dispatch_async_f(firstSerialQueue, NULL, secondIteration);
    dispatch_async_f(firstSerialQueue, NULL, thirdIteration);
    self.window = [[UIWindow alloc] initWithFrame:
                   [[UIScreen mainScreen] bounds]];
    self.window.backgroundColor = [UIColor whiteColor]; [self.window makeKeyAndVisible];
    return YES;
}



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值