Параллелизм в среде Cocoa, Mac OS, iOS

Операция очереди (Operation Queues).

Cocoa операции - объектно-ориентированный способ инкапсуляции работы, которую вы хотите выполнить асинхронно. Операции предназначены для использования либо в сочетании с операцией очереди или сами по себе. Потому как операции Objective-C основанные, они чаще всего используются в Cocoa-приложениях в Mac OS X и iOS.

Объекты операции

Объект операции является экземпляром NSOperation класса (в Foundation framework), который используется для инкапсуляции работы, которую Вы хотите, чтобы ваше приложение выполняло. Класс NSOperation сам по себе является абстрактным базовым классом, который должен быть реализован подклассом для того, чтобы сделать любой труд полезным. Несмотря на то что он абстрактный, этот класс действительно обеспечивает значительный объем инфраструктуры, чтобы минимизировать количество работы, которую вы должны сделать в вашем подклассе. Кроме того, в Foundation framework предусмотрено два конкретных подкласса, которые можно использовать как они есть из вашего существующего кода. Таблица 2-1 содержит список этих классов, а также резюме о том как вы используете каждый из них.

Таблица 2-1 классы операций Foundation framework.

КлассОписание
NSInvocationOperationКласс, используемый как есть, создающий объект операции на основе объекта и селектора из вашего приложения. Вы можете использовать этот класс в тех случаях, когда у вас уже есть метод, который выполняет необходимые задачи. Потому как он не требует наследования, вы также можете использовать этот класс для создания объектов операции более динамичным образом.
NSBlockOperationКласс, используемый как есть, для выполнения одного или нескольких блоковых объектов одновременно. Поскольку он может выполнять более одного блока, блоковый объект операции работает с использованием семантических групп; только тогда, когда все связанные блоки завершат свою работу, сама операция считается завершенной.
NSOperationБазовый класс для определения пользовательских объектов операции. Подклассы NSOperation предоставляют полный контроль над выполнением собственных операций, в том числе возможность изменять значения по умолчанию, контролировать каким образом выполняется ваша работа, и сообщать о своем состоянии.

Все операции объектов поддерживают следующие основные характеристики:

  • Поддержка создания основанных на графах зависимостей между объектами операции, предотвращая запуск операции до завершения других, от результатов которых она зависит.
  • Поддержка дополнительного блока завершения, который выполняется после завершения основных задач операции. (Только в Mac OS X 10.6 и более поздних версиях).
  • Поддержка для мониторинга изменений в состояние выполнения ваших операций с использованием KVO (Ключ-значение-Наблюдение) уведомлений.
  • Поддержка приоритетов операций, таким образом, влияя на их относительный порядок исполнения.
  • Поддержка семантики отмены, которая позволяет остановить операцию во время ее выполнения.

Операции призваны помочь Вам повысить уровень параллелизма в вашем приложении. Операции также являются хорошим способом организации и инкапсуляции поведения приложения в простые дискретные отрезки. Вместо того, чтобы запускать код относительно емкой задачи на главном потоке приложения, вы можете представить один или несколько объектов операции по очереди и пусть соответствующая задача выполняется асинхронно на одном или нескольких отдельных потоках.

Создание NSInvocationOperation объекта

@implementation MyCustomClass
- (NSOperation*)taskWithData:(id)data
{
   NSInvocationOperation* theOp = [[[NSInvocationOperation alloc] initWithTarget:self
                    selector:@selector(myTaskMethod:) object:data] autorelease];

   return theOp;
}

// Данный метод выполняет работу в задаче

- (void)myTaskMethod:(id)data
{
    ...
    // Код, выполняющий работу
    ...
}

@end

Создание NSBlockOperation объекта

При создании блока операции, обычно добавляют по крайней мере один блок во время инициализации, в дальнейшем вы можете добавить больше блоков. Когда приходит время для выполнения NSBlockOperation, объект представляет все свои блоки с приоритетом по умолчанию в параллельные очереди отправки. Объект ждет, пока все блоки завершатся. Когда последний блок завершается, работа объекта знаменует свое окончание. Таким образом, можно использовать блок операций, чтобы отслеживать выполнение группы блоков, так же, как вы будете использовать поток, чтобы объединить результаты из нескольких потоков. Разница состоит в том, что, поскольку сама операция блока выполняется в отдельном потоке, другие потоки приложения могут продолжать делать работу в ожидании завершения операции блока.

NSBlockOperation* theOp = [NSBlockOperation blockOperationWithBlock: ^{
  NSLog(@"Начало операции.\n");
  // Выполнение какой либо задачи.
}];

После создания объекта блока операции вы можете добавить больше блоков, используя метод addExecutionBlock:. Если вам нужно выполнить блоки последовательно, вы должны представить их непосредственно на желаемую очередь отправки.

Определение пользовательского объекта операции

Если операция блока и объектов вызова операции не вполне отвечают потребностям вашего приложения, вы можете создать подкласс NSOperation непосредственно, и добавить все, что вам нужно. Класс NSOperation обеспечивает общую точку для всех подклассов объектов операции. Класс также обеспечивает значительную часть инфраструктуры для обработки большей части работы, необходимой для зависимостей и KVO уведомлений.

Определение однопоточной операции намного проще, чем определение одновременной работы. Для однопоточной операции, все, что вам нужно делать, это выполнять ваши основные задачи и адекватно реагировать на отмену выполнения; существующая инфраструктура класса делает всю другую работу за вас. Для одновременной работы, необходимо заменить некоторую существующую инфраструктуру с помощью специального программного кода. Ниже будет показано, как реализовать оба типа объекта.

Выполнение основной задачи

Как минимум, каждая операция объектов должна реализовывать по крайней мере, следующие методы:

  • Пользовательский метод инициализации
  • main

Вам нужен специальный метод инициализации, чтобы привести объект операции в определенное состояние и пользовательский метод main для выполнения ваших задач. Вы можете реализовать, по мере необходимости, дополнительные методы, такие, как:

  • Пользовательские методы, которые вы собираетесь вызывать из реализации вашего метода main
  • Методы доступа к значениям данных и результатов операции (установки и чтения)
  • Метод dealloc для очистки памяти, выделенной в результате работы Вашего объекта
  • Методы NSCoding протокола, позволяеющие архивировать и разархивировать объект операции

Определение простого объекта операции

@interface MyNonConcurrentOperation : NSOperation {
   id myData;
}
-(id)initWithData:(id)data;
@end

@implementation MyNonConcurrentOperation
- (id)initWithData:(id)data {
   if (self = [super init])
      myData = [data retain];
   return self;
}

- (void)dealloc {
   [myData release];
   [super dealloc];
}

-(void)main {
   @try {
      NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
      // Выполняем работу с myData и выводим, сохраняем результат

      [pool release];
   }
   @catch(...) {
      // Не перевозбуждаем исключение
   }
}
@end

Ответ на событие отмены

После того, как начинается операция выполнения, она продолжает выполнять свою задачу, пока не закончится или пока ваш код явно не отменит операцию. Отмена может произойти в любое время, еще до начала операции выполнения. Несмотря на то, что, класс NSOperation предоставляет возможность для клиентов, по отмене операции, учет события отмены является добровольным. Если операция была прямо прекращена, то не предоставится способа вернуть память или ресурсы, которые были выделены. В результате, объекты операции, как ожидается, проверяют на событие отмены и завершения в середине своей операции.

Для поддержки отмены объекта операции, все что вам нужно сделать, это периодически вызывать метод объекта isCancelled из вашего пользовательского кода и немедленно возвращаться, если он не возвращает YES. Поддержка отмены важна независимо от продолжительности вашей работы или вашего подкласса NSOperation напрямую или с помощью одного из своих конкретных подклассов. Метод isCancelled сам по себе очень легкий и может вызываться часто без значительного снижения производительности. При проектировании объектов операции, вы должны рассматривать вызов метода isCancelled в следующих местах в коде:

  • Непосредственно перед выполнением любой реальной работы
  • По крайней мере, один раз в течение каждой итерации цикла, или чаще, если каждая итерация относительно долгая
  • В любой точке в коде, где было бы относительно легко прервать операцию

Пример реализации проверки на прерывание в цикле.

- (void)main {
   @try {
      NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
      BOOL isDone = NO;

      while (![self isCancelled] && !isDone) {
          // Выполняем работу, по окончании устанавливаем isDone в YES

      }
      [pool release];
   }
   @catch(...) {
      // Не перевозбуждаем исключение

   }
}

Хотя пример выше не содержит код очистки, ваш собственный код должен быть уверен, что освободит любую выделенную память или ресурс.

Настройка операции для параллельного выполнения

Операции объектов выполняются в синхронном режиме по умолчанию, то есть они выполняют свои задачи в потоке, который вызывает их методом start. Поскольку операции очереди предоставляют потоки для однопоточной операции, большинство операций все еще работают асинхронно. Однако, если вы планируете выполнять операции вручную и все еще хоте, чтобы они выполнялись асинхронно, вы должны принять надлежащие меры, чтобы гарантировать, что они делают. Это можно сделать путем определения вашей операции объекта как одновременной операции.

Таблица 2-2, методы, которые обычно переопределяют для реализации одновременной операции.

МетодОписание
start(Обязательно) всех одновременных операций следует переопределить этот метод и заменить по умолчанию со своей собственной реализацией. Для выполнения операции вручную, вы называете этот start метод. Таким образом, реализация данного метода является отправной точкой для работы, где бы вы ни создали поток или в другой среде выполнения. Ваша реализация не должна вызывать super в любое время.
main(Необязательный) Этот метод обычно используется для реализации задач, связанных с объектами операции. Хотя вы могли бы выполнять задачи в методе start реализации задачи, использование этого метода может привести к более чистому разделению ваших настроек и кода задачи.
isExecuting
isFinished
(Обязательно) параллельные операции несут ответственность за создание своей среды исполнения и представления состояния среды, для внешних клиентов. Таким образом, одновременные операции должны поддерживать некоторую информацию о состоянии, чтобы знать, когда она выполняет свою задачу, и когда закончила эту задачу. Операция должна сообщить, о своем состоянии с помощью этих методов.
Ваша реализация этих методов должна быть безопасной для вызова из других потоков одновременно. Вы также должны генерировать соответствующие уведомления KVO для ожидаемых ключевых путей при изменении значений, представленных этими методами.
isConcurrent(Обязательно) определяет операцию одновременной операции, переопределите этот метод и верните YES.

Пример определения одновременной операции MyOperation

@interface MyOperation : NSOperation {
    BOOL        executing;
    BOOL        finished;
}

- (void)completeOperation;
@end

@implementation MyOperation
- (id)init {
    self = [super init];
    if (self) {
        executing = NO;
        finished = NO;
    }
    return self;
}

- (BOOL)isConcurrent {
    return YES;
}

- (BOOL)isExecuting {
    return executing;
}

- (BOOL)isFinished {
    return finished;
}
@end

Реализация метода start класса MyOperation

- (void)start {
   // Всегда проверяйте на отмену до запуска задачи.
   if ([self isCancelled])
   {
      // Необходимо привести к завершенному состоянию, если произошла отмена.
      [self willChangeValueForKey:@"isFinished"];
      finished = YES;
      [self didChangeValueForKey:@"isFinished"];
      return;
   }

   //если не произошла отмена, продолжаем выполнение задачи

   [self willChangeValueForKey:@"isExecuting"];
   [NSThread detachNewThreadSelector:@selector(main) toTarget:self withObject:nil];
   executing = YES;
   [self didChangeValueForKey:@"isExecuting"];
}

Дополнение для класса MyOperation

- (void)main {
   @try {
       NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];

       // Основная работа операции распологается здесь.

       [self completeOperation];
       [pool release];
   }
   @catch(...) {
      // Не перевозбуждаем исключение
   }
}

- (void)completeOperation {
    [self willChangeValueForKey:@"isFinished"];
    [self willChangeValueForKey:@"isExecuting"];

    executing = NO;
    finished = YES;

    [self didChangeValueForKey:@"isExecuting"];
    [self didChangeValueForKey:@"isFinished"];
}

Даже если операция отменяется, вы всегда должны уведомить KVO, что ваша операции будет закончена. Объект операции зависит от завершения других объектов операции, он контролирует ключевой путь isFinished для этих объектов. Только тогда, когда все объекты, сообщают, что они закончили, он дает сигнал зависимой операции, чтобы она была готова к запуску. В противном случае при создании уведомления окончания может, следовательно, предотвратить выполнение других операций в приложении.

Поддержание соответствия KVO

NSOperation - класс (KVO), совместимый по следующим ключевым путям:

  • isCancelled
  • isConcurrent
  • isExecuting
  • isFinished
  • isReady
  • dependencies
  • queuePriority
  • completionBlock

При переопределении метода start или если сделаны любые значительные настройки объекта NSOperation, кроме переопределения main, вы должны убедиться, что ваш пользовательский объект остается KVO совместимым для этих ключевых путей. При переопределении метода start, основные пути которые больше всего должны Вас волновать isExecuting и isFinished. Эти ключевые пути чаще всего выступают в роли пострадавших от повторной реализации этого метода.

Хотя вы можете создать KVO уведомления для других ключевых путей NSOperation, вряд ли Вам это когда-нибудь понадобится. Если вам нужно отменить операцию, вы можете просто вызвать существующий метод cancel. Кроме того, не должно быть особой необходимости изменять информацию приоритета операции объекта. Наконец, если ваша операция способна изменить свой ​​статус параллелизма динамически, вам не нужно обеспечить KVO уведомления с ключевым путем isConcurrent.

Настройка выполнения поведения объекта операции

Конфигурация объектов операции происходит после того, как они были созданы, но, прежде чем добавлены в очередь. Типы конфигураций, описанных в этом разделе могут быть применены ко всем объектам операции, независимо от того, являются они подклассами NSOperation или используют существующий подкласс.

Настройка взаимодействия зависимостей

Зависимости, это способ сериализации выполнения отдельных объектов операции. Операция, которая зависит от других операций, не может начаться до выполнения всех операций, от которых она зависит. Таким образом, вы можете использовать зависимости для создания простых, один к одному, зависимостей между двумя объектами орерации или строить сложные графики зависимостей объекта.

Для установления зависимости между двумя объектами операции, используют addDependency:, метод NSOperation. Этот метод создает одностороннюю зависимость от текущей операции объекта в целевую операцию, указанную в качестве параметра. Эта зависимость означает, что текущий объект не может начать выполнение до завершения выполнения целевого объекта. Зависимости также не ограничиваются операцией в той же очереди. Объекты операции управляют своими зависимостями, и поэтому вполне приемлемо при создании зависимостей между операциями, добавить их в различные очереди. Одна вещь, которая не является приемлемой, заключается в создании циклических зависимостей между операциями.

Когда закончина работа по установлению всех зависимостей операции, объект операции обычно становится готов для выполнения. (Если вы настроите поведение метода isReady, готовность работы определяется в соответствии с установиленными вами критериями). Если объект операции находится в очереди, очередь может начать выполнение этой операции в любое время. Если вы планируете выполнять операции вручную, вызовите метод операции start.

Изменение приоритета выполнения операции

Для операции, добавленной в очередь, порядок выполнения определяется сначала готовностью очереди операции, а затем ее относительным приоритетом. Готовность определяется зависимостями операции от других операций, а уровень приоритета является атрибутом функционирования объекта. По умолчанию все новые объекты обладают "нормальным" приоритетом, но вы можете его увеличить или уменьшить, вызвав метод setQueuePriority:.

Изменение приоритета базового потока

В Mac OS X 10.6 и более поздних версий можно настроить приоритет выполнения базовых потоков операции. Политика потоков в системе сама управляется ядром, но в целом пртоки с более высоким приоритетом имеют более широкие возможности для запуска, чем с более низким. В объектах операции, задают приоритет потока как значение с плавающей точкой в диапазоне от 0.0 до 1.0, где 0.0 является самым низким приоритетом и 1.0 является самым высоким приоритетом. Если вы явно не укажете приоритет потока, то операция выполняется с приоритетом потока по умолчанию равным 0.5.

Чтобы задать приоритет потока операций, вы должны вызвать метод setThreadPriority: вашего рабочего объекта прежде, чем добавить его в очередь (или выполненить вручную). Когда приходит время для выполнения операции, метод start по умолчанию использует значение, указанное для изменения приоритета текущего потока. Это новый приоритет остается в силе в течение всего срока выполнения метода main вашего объекта операции. Весь остальной код (в том числе завершение блока вашей операции) запускается с приоритетом потоков по умолчанию. Если вы создаете одновременное выполнение и, следовательно, изменяете метод start, необходимо настроить приоритет собственного потока.

Настройка Завершающего блока

В Mac OS X 10.6 и выше, операция может выполняться c завершающим блоком, когда его основная задача завершает выполнение. Вы можете использовать завершающий блок для выполнения любой работы, которую вы не считаете частью основной задачи. Например, вы можете использовать этот блок для уведомления заинтересованных клиентов, что сама операция была завершена. Одновременно объект операции может использовать этот блок для получения уведомления окончания KVO.

Чтобы установить блок завершения, используйте setCompletionBlock:, метод NSOperation. Блок, передаемый в этот метод не должен содержать никаких аргументов и не должен возвращать никакого значения.

close

Блок объект Читать более подробно

Блок объекты - синтаксические и выполняемые функций C-уровня, которые позволяют составить функцию выражения, которая может быть переданы в качестве аргумента, при необходимости хранения и использования в многопоточных приложениях. Функция выражения может ссылаться и может сохранить доступ к локальным переменным. В других языках и средах, блок объекта иногда называют закрытием или лямбдой. Вы можете использовать блок, когда вы хотите создать рабочие модули (то есть сегменты кода), которые могут быть переданы как будто они являются значениями. Блоки предлагают более гибкое программирование и больше возможностей. Вы можете использовать их, например, для написания обратных вызовов или для выполнения операции для всех элементов в коллекции.

Объявление блока

Во многих случаях, можно использовать встроенные блоки, поэтому вам не нужно объявлять их. Синтаксиса декларации, однако, похож на стандартный синтаксис указателей на функции, за исключением того, что вы используете вставки (^) вместо звездочки указателя (*). Например, следующий код объявляет переменную aBlock, который ссылается на блок, который требует трех параметров и возвращает значение с плавающей точкой:

float (^aBlock)(const int*, int, float);

Создание блока

Вы можете использовать вставки (^) оператора для обозначения начала и точку с запятой, чтобы обозначить конец блока выражения. В следующем примере объявляется простой блок и относит его к ранее объявленной блоком переменной (oneFrom):

int (^oneFrom)(int);

oneFrom = ^(int anInt) {
    return anInt - 1;
};

Закрытие точкой с запятой требуется в качестве стандарта C конца линии маркера.

Если вы явно не объявляете возвращаемое значение выражения блока, оно может автоматически выводится из содержимого блока.

Блок-изменяемые переменные

Вы можете использовать __block модификатор хранения c переменными, объявленными локально с вложениями лексической области, чтобы обозначить, что такие переменные должны быть предоставлены по ссылке в блоке и являются изменяемыми. Любые изменения находят свое отражение в окружающей лексической области, в том числе любых других блоков, определенных в той же ограждающей лексической области.

Использование блоков

Если вы объявляете блок в качестве переменной, вы можете использовать его так же, как функцию. В следующем примере печатается 9.

printf("%d\n", oneFrom(10));

Обычно, передают блок в качестве аргумента функции или методу. В этих случаях часто создают блок линию.

В следующем примере определяется, содержит ли объект NSSet слово, указанное в локальной переменной и задает значение другой локальной переменной (found) в YES (и прекращает поиск), если он выполняется. В этом примере, found объявлена ​​как переменная __block.

__block BOOL found = NO;
NSSet *aSet = [NSSet setWithObjects: @"Alpha", @"Beta", @"Gamma", @"X", nil];
NSString *string = @"gamma";

[aSet enumerateObjectsUsingBlock:^(id obj, BOOL *stop) {
    if ([obj localizedCaseInsensitiveCompare:string] == NSOrderedSame) {
        *stop = YES;
        found = YES;
    }
}];

// В этой точке, found == YES

В этом примере блок, содержащийся в списке аргументов метода. Блок также использует стек локальных переменных.

Операции сравнения

Одной из наиболее распространенных операций, которые вы выполняете с блоками в среде Cocoa сравнение двух объектов, сортировка содержимого массива. Среда выполнения Objective-C определяет блок типа NSComparator для использования этих сравнений.

 
 
homeЗаметили ошибкукарта сайта 
   Made on a Mac