iOS. Приемы программирования Нахавандипур Вандад
if ([folders count] == 0){
return nil;
}
/* Получаем первый каталог. */
NSString *documentsFolder = [folders objectAtIndex:0];
/* Прикрепляем имя файла к концу пути документа. */
return [documentsFolder
stringByAppendingPathComponent:@"list.txt"];
}
— (BOOL) hasFileAlreadyBeenCreated{
BOOL result = NO;
NSFileManager *fileManager = [[NSFileManager alloc] init];
if ([fileManager fileExistsAtPath: [self fileLocation]]){
result = YES;
}
return result;
}
А вот теперь очень важный нюанс. Мы хотим сохранить на диске массив из 10 000 случайных чисел, если, и только если мы не создавали такой массив на диске раньше. В противном случае мы сразу загрузим массив с диска. Если же прежде мы не создавали этот массив на диске, то сначала создадим его, а потом перейдем к загрузке массива с диска. В итоге, если считывание массива с диска пройдет успешно, мы отсортируем этот массив в порядке возрастания и, наконец, отобразим результаты для пользователя в графическом интерфейсе. Реализацию отображения результатов пользователю оставляю вам для самостоятельной работы.
— (void) viewDidAppear:(BOOL)paramAnimated{
[super viewDidAppear: paramAnimated];
dispatch_queue_t concurrentQueue =
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
/* Если мы еще не отсортировали массив из 10 000 случайных чисел
на диске ранее, сгенерируем эти числа сейчас, а потом сохраним
их на диск в массиве. */
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]];
}
/* Теперь записываем массив на диск. */
[arrayOfRandomNumbers writeToFile: [self fileLocation]
atomically: YES];
});
}
__block NSMutableArray *randomNumbers = nil;
/* Считываем числа с диска и сортируем их в порядке возрастания. */
dispatch_sync(concurrentQueue, ^{
/* Если файл на данный момент уже создан, занимаемся его считыванием. */
if ([self hasFileAlreadyBeenCreated]){
randomNumbers = [[NSMutableArray alloc]
initWithContentsOfFile: [self fileLocation]];
/* Теперь сортируем числа. */
[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){
/* Обновляем пользовательский интерфейс, задействуя числа
из массива randomNumbers. */
}
});
});
}
Функционал GCD далеко не ограничивается синхронным или асинхронным выполнением блоков кода или функций. В разделе 7.9 вы научитесь группировать блоковые объекты и готовить их к выполнению в диспетчерской очереди. Кроме того, рекомендую вам изучить разделы 7.7. и 7.8, где говорится о прочих функциях, которые предоставляются программисту в GCD.
См. также
Разделы 7.4, 7.7 и 7.8.
7.7. Выполнение задач после задержки с помощью GCD
Постановка задачи
Требуется выполнить код, но после определенной задержки. Задержку планируется указывать с помощью GCD.
Решение
Воспользуйтесь функциями dispatch_after и dispatch_after_f.
Обсуждение
Работая с фреймворком Core Foundation, можно активизировать селектор в объекте по истечении заданного временного промежутка с помощью метода performSelector: withObject: afterDelay:, относящегося к классу NSObject. Например:
— (void) printString:(NSString *)paramString{
NSLog(@"%@", paramString);
}
— (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;
}
В данном примере мы приказываем среде времени исполнения вызвать метод printString: после трехсекундной задержки. Ту же операцию можно осуществить и в GCD с помощью функций dispatch_after и dispatch_after_f. Обе эти функции описаны далее.
• dispatch_after — направляет блоковый объект в диспетчерскую очередь по истечении заданного периода времени, указываемого в наносекундах. Эта функция требует следующих параметров:
• задержка в наносекундах — количество наносекунд, в течение которых длится ожидание в определенной диспетчерской очереди в GCD (указываемой во втором параметре), после чего выполняется блоковый объект (задаваемый в третьем параметре);
• диспетчерская очередь — диспетчерская очередь, в которой должен быть выполнен блоковый объект (указываемый в третьем параметре) после определенной задержки (задаваемой в первом параметре);
• блоковый объект — блоковый объект, который должен быть активизирован в заданной диспетчерской очереди по истечении заданного количества наносекунд. Блоковый объект не должен иметь возвращаемого значения и не должен принимать никаких параметров (см. раздел 7.1).
• dispatch_after_f — направляет функцию на языке C в GCD для выполнения по истечении указанного периода времени, задаваемого в наносекундах. Данная функция принимает четыре параметра:
• задержка в наносекундах — количество наносекунд, в течение которых длится ожидание в определенной диспетчерской очереди в GCD (указываемой во втором параметре), после чего выполняется заданная функция (задаваемая в четвертом параметре);
• диспетчерская очередь — диспетчерская очередь, в которой должна быть выполнена функция на языке C (указываемая во втором параметре) после определенной задержки (задаваемой в первом параметре);
• контекст — адрес в памяти, по которому находится определенное значение, относящееся к неупорядоченному массиву данных (куче). Это значение должно передаваться функции C. Подробнее об этом говорилось в разделе 7.4;
• функция на языке C — адрес функции на языке C, которая должна быть выполнена по истечении определенного периода времени (указываемого в первом параметре) в заданной диспетчерской очереди (указываемой во втором параметре).
Хотя задержки рассчитываются в наносекундах, размерность задержки при диспетчеризации определяется самой системой iOS, и эта величина может быть менее точной, чем та, которую вы указываете в наносекундах.
Сначала рассмотрим пример работ с функцией 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){
/* Здесь выполняются требуемые операции. */
});
// Точка переопределения для настройки после запуска приложения
self.window.backgroundColor = [UIColor whiteColor];
[self.window makeKeyAndVisible];
return YES;
}
Как видите, параметр задержки в наносекундах для функций dispatch_after и dispatch_after_f должен относиться к типу dispatch_time_t, который является абстрактным представлением абсолютного времени. Чтобы получить значение этого параметра, можно пользоваться функцией dispatch_time так, как показано в данном образце кода. Вот параметры, которые можно сообщать функции dispatch_time.
• Исходное время — если обозначить этот параметр через B, а приращение времени (Delta Parameter) — через D, то результирующее время от этой функции будет равно B+D. Для этого параметра можно задать значение DISPATCH_TIME_NOW, определив таким образом в качестве базового времени настоящий момент, а потом указать приращение, добавляемое к этому времени, используя дельта-параметр.
Приращение, добавляемое к базовому времени, — этот параметр дает количество наносекунд, добавляемых к параметру исходного времени для получения результата данной функции.
Например, чтобы задать временной промежуток 3 с начиная от настоящего момента, можно написать следующий код:
dispatch_time_t delay =
dispatch_time(DISPATCH_TIME_NOW, 3.0f * NSEC_PER_SEC);
А вот так задается период 0,5 с от настоящего момента:
dispatch_time_t delay =
dispatch_time(DISPATCH_TIME_NOW, (1.0 / 2.0f) * NSEC_PER_SEC);
Теперь рассмотрим, как можно использовать функцию dispatch_after_f:
void processSomething(void *paramContext){
/* Здесь происходит обработка. */
NSLog(@"Processing…");
}
— (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]];
// Точка переопределения для настройки после запуска приложения
self.window.backgroundColor = [UIColor whiteColor];
[self.window makeKeyAndVisible];
return YES;
}
См. также
Разделы 7.1 и 7.4.
7.8. Однократное выполнение задач с помощью GCD
Постановка задачи
Необходимо убедиться в том, что определенный фрагмент кода выполняется один раз за весь жизненный цикл приложения, даже если он вызывается неоднократно из разных точек программы (в качестве примера можно привести инициализацию синглтона).
Решение
Воспользуйтесь функцией 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];