Постановка задачи
Необходимо убедиться в том, что определенный фрагмент кода выполняется один раз за весь жизненный цикл приложения, даже если он вызывается неоднократно из разных точек программы (в качестве примера можно привести инициализацию синглтона).
Решение
Воспользуйтесь функцией dispatch_once.
Обсуждение
Выделение и инициализация синглтона — одна из таких задач, которые должны происходить один, и только один раз за весь жизненный цикл приложения. Уверен, что вы можете вспомнить и другие аналогичные сценарии.
GCD позволяет указывать идентификатор для фрагмента кода при попытке выполнить этот код. Если GCD обнаруживает, что данный идентификатор уже передавался фреймворку ранее, то система не будет вновь выполнять этот блок кода. Функция, которая обеспечивает выполнение подобных задач, называется dispatch_once. Она может принимать два параметра.
• Маркер — маркер типа dispatch_once_t, содержащий сгенерированную GCD метку при первом выполнении блока кода. Если вы хотите, чтобы блок кода был выполнен лишь один раз, нужно указывать для данного метода один и тот же маркер независимо от того, когда он активизируется в приложении. Такой пример мы вскоре рассмотрим.
Блоковый объект — блоковый объект, выполняемый не более одного раза. Блоковый объект не возвращает никаких значений и не принимает никаких параметров.
dispatch_once всегда выполняет свою задачу в актуальной очереди, используемой кодом, который делает вызов. Это может быть как последовательная, так и параллельная или главная очереди.
Например:
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 дважды с помощью функции dispatch_once, но на самом деле GCD выполняет этот блоковый объект лишь однажды, поскольку идентификатор, передаваемый функции dispatch_once, оба раза один и тот же.
В руководстве Cocoa Fundamentals Guide (Руководство по основам Cocoa) (https://developer.apple.com/library/ios/#documentation/General/Conceptual/DevPedia-CocoaCore/Singleton.html) Apple объясняется, как создавать синглтон. Исходный код довольно старый и еще не обновлен с учетом использования GCD и автоматического подсчета ссылок. Мы можем изменить эту модель, чтобы можно было пользоваться 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. Объединение задач в группы с помощью GCD
Постановка задачи
Требуется объединять блоки кода в группы и гарантировать, что GCD будет выполнять все задачи одну за другой, выстраивая таким образом зависимости между ними.
Решение
Для создания групп в GCD пользуйтесь функцией dispatch_group_create.
Обсуждение
GCD позволяет создавать группы. Пользуясь группами, можно поместить несколько задач в одном месте, выполнить их все, а по завершении работы получить об этом уведомление от GCD. Такая технология имеет большое прикладное значение. Допустим, например, что у вас есть приложение с пользовательским интерфейсом и вы хотите перезагрузить его компоненты в этом пользовательском интерфейсе. В пользовательском интерфейсе у вас имеется табличный вид, прокручиваемый вид и вид с изображением. Вы хотите перезагрузить содержимое этих компонентов с помощью следующих методов:
— (void) reloadTableView{
/* Здесь перезагружается табличный вид. */
NSLog(@"%s", __FUNCTION__);
}
— (void) reloadScrollView{
/* Здесь выполняется работа. */
NSLog(@"%s", __FUNCTION__);
}
— (void) reloadImageView{
/* Здесь перезагружается вид с изображением. */
NSLog(@"%s", __FUNCTION__);
}
На данный момент эти методы пусты, но вы можете позже поместить в них важный код, связанный с пользовательским интерфейсом. Сейчас мы собираемся вызвать эти три метода один за другим и узнать, когда GCD закончит вызывать эти методы, в результате чего мы отобразим соответствующее сообщение для пользователя. Для этого нам придется воспользоваться группой. При работе с группами в GCD необходимо иметь представление о трех функциях:
• dispatch_group_create — создает описатель группы;
• dispatch_group_async — отправляет блок кода в группу для выполнения. Необходимо указать диспетчерскую очередь, в которой должен выполняться этот блок кода, а также группу, к которой этот блок кода относится;
• dispatch_group_notify — позволяет отправить блоковый объект, который необходимо выполнить после того, как все задачи, направленные в группу для выполнения, закончат свою работу. Эта функция также позволяет указывать диспетчерскую очередь, в которой должен выполняться данный блоковый объект.
Рассмотрим пример. Как объяснялось ранее, в этом примере мы собираемся активизировать методы reloadTableView, reloadScrollView и reloadImageView один за другим, а потом отобразить для пользователя сообщение о том, что задача выполнена. Для достижения этой цели применим мощные групповые функции, присущие GCD:
— (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(taskGroup, mainQueue, ^{
[self reloadTableView];
});
/* Перезагружаем прокручиваемый вид в главной очереди. */
dispatch_group_async(taskGroup, mainQueue, ^{
[self reloadScrollView];
});
/* Перезагружаем вид с изображением в главной очереди. */
dispatch_group_async(taskGroup, mainQueue, ^{
[self reloadImageView];
});
/* Когда все это будет сделано, диспетчеризуем следующий блок. */
dispatch_group_notify(taskGroup, mainQueue, ^{
/* Здесь происходит обработка. */
[[[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;
}
Кроме работы с функцией dispatch_group_async, можно также направлять асинхронные функции на языке C, используя функцию dispatch_group_async_f.
GCDAppDelegate — это просто имя класса, из которого взят пример. Данное имя класса мы будем использовать для приведения типа контекстного объекта так, чтобы компилятор понимал наши команды.
Вот так:
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);
/* Когда все это будет сделано, диспетчеризуем следующий блок. */
dispatch_group_notify(taskGroup, mainQueue, ^{
/* Здесь происходит обработка. */
[[[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;
}
Поскольку функция dispatch_group_async_f принимает функцию на языке C как блок кода для исполнения, у функции C должна быть ссылка на self, чтобы она могла активизировать методы экземпляра актуального объекта, где реализована функция C. Вот почему self передается как указатель контекста в функции dispatch_group_async_f. Подробнее о контекстах и функциях C рассказано в разделе 7.4.
После того как все поставленные задачи будут завершены, пользователь увидит примерно такую картинку, как на рис. 7.3.
Рис. 7.3. Управление группой задач в GCD
См. также
Раздел 7.4.
7.10. Создание собственных диспетчерских очередей с помощью GCD