Очереди работы

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

Эта глава показывает Вам, как определить и использовать операции.

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

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

Табличные 2-1  классы Работы платформы Основы

Класс

Описание

NSInvocationOperation

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

Для получения информации о том, как использовать этот класс, посмотрите Создание Объекта NSInvocationOperation.

NSBlockOperation

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

Для получения информации о том, как использовать этот класс, посмотрите Создание Объекта NSBlockOperation. Этот класс доступен в OS X v10.6 и позже. Для получения дополнительной информации о блоках, посмотрите, что Блоки Программируют Темы.

NSOperation

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

Для получения информации о том, как определить пользовательские объекты операции, посмотрите Определение Пользовательского Объекта операции.

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

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

Параллельный по сравнению с непараллельными операциями

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

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

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

Для получения информации о том, как создать параллельную работу, посмотрите Операции Конфигурирования для Параллельного Выполнения и Ссылки класса NSOperation.

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

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

Процесс для создания работы вызова является прямым. Вы создаете и инициализируете новый экземпляр класса, передавая требуемый объект и селектор для выполнения к методу инициализации. Перечисление 2-1 показывает два метода от пользовательского класса, демонстрирующие процесс создания. taskWithData: метод создает новый объект вызова и предоставляет его имя другого метода, содержащего реализацию задачи.

Перечисление 2-1  , создающее NSInvocationOperation объект

@implementation MyCustomClass
- (NSOperation*)taskWithData:(id)data {
    NSInvocationOperation* theOp = [[NSInvocationOperation alloc] initWithTarget:self
                    selector:@selector(myTaskMethod:) object:data];
 
   return theOp;
}
 
// This is the method that does the actual work of the task.
- (void)myTaskMethod:(id)data {
    // Perform the task.
}
@end

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

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

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

Перечисление 2-2 показывает простой пример того, как создать NSBlockOperation объект. Сам блок не имеет никаких параметров и никакого значительного результата возврата.

Перечисление 2-2  , создающее NSBlockOperation объект

NSBlockOperation* theOp = [NSBlockOperation blockOperationWithBlock: ^{
      NSLog(@"Beginning operation.\n");
      // Do some work.
   }];

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

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

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

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

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

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

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

Перечисление 2-3 показывает стартовый шаблон для пользовательского NSOperation подкласс. (Это перечисление не показывает, как обработать отмену, но действительно показывает методы, которые Вы обычно имели бы. Для получения информации об обработке отмены посмотрите События Отмены Ответа.) Метод инициализации для этого класса берет отдельный объект в качестве параметра данных и хранит ссылку на него в объекте операции. main метод якобы работал бы над тем объектом данных прежде, чем возвратить результаты назад Вашему приложению.

Перечисление 2-3  , Определяющее простой объект операции

@interface MyNonConcurrentOperation : NSOperation
@property id (strong) myData;
-(id)initWithData:(id)data;
@end
 
@implementation MyNonConcurrentOperation
- (id)initWithData:(id)data {
   if (self = [super init])
      myData = data;
   return self;
}
 
-(void)main {
   @try {
      // Do some work on myData and report the results.
   }
   @catch(...) {
      // Do not rethrow exceptions.
   }
}
@end
 

Для подробного примера того, как реализовать NSOperation разделите на подклассы, посмотрите NSOperationSample.

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

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

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

  • Сразу перед выполнением любой фактической работы

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

  • В любых точках в Вашем коде, где было бы относительно просто прервать работу

Перечисление 2-4 обеспечивает очень простой пример того, как реагировать на события отмены в main метод объекта операции. В этом случае, isCancelled метод вызывают каждый раз через a while цикл, допуская быстрый выход перед работой начинается и снова равномерно.

Перечисление 2-4  , Отвечающее на запрос отмены

- (void)main {
   @try {
      BOOL isDone = NO;
 
      while (![self isCancelled] && !isDone) {
          // Do some work and set isDone to YES when finished
      }
   }
   @catch(...) {
      // Do not rethrow exceptions.
   }
}

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

Конфигурирование операций для параллельного выполнения

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

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

Табличные 2-2  Методы для переопределения для параллельных операций

Метод

Описание

start

(Требуемый) Все параллельные операции должны переопределить этот метод и заменить поведение по умолчанию их собственной реализацией. Для выполнения работы вручную Вы вызываете start метод. Поэтому Ваша реализация этого метода является начальной точкой для Вашей работы и - где Вы устанавливаете поток или другую среду выполнения, в которой можно выполнить Вашу задачу. Ваша реализация не должна вызывать super в любое время.

main

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

isExecuting

isFinished

(Требуемые) операции Concurrent ответственны за установку их среды выполнения и создание отчетов о состоянии той среды внешним клиентам. Поэтому параллельная работа должна поддержать некоторую информацию состояния для знания, когда это выполняет свою задачу и когда это закончило ту задачу. Это должно тогда сообщить что состояние с помощью этих методов.

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

isConcurrent

(Требуемый) идентифицировать работу как параллельную работу, переопределите этот метод и возврат YES.

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

Перечисление 2-5 показывает интерфейс и часть реализации MyOperation класс. Реализации isConcurrent, isExecuting, и isFinished методы для MyOperation класс является относительно прямым. isConcurrent метод должен просто возвратиться YES указать, что это - параллельная работа. isExecuting и isFinished методы просто возвращаемые значения, сохраненные в переменных экземпляра самого класса.

Перечисление 2-5  , Определяющее параллельную работу

@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

Перечисление 2-6 показывает start метод MyOperation. Реализация этого метода минимальна, чтобы продемонстрировать задачи, которые абсолютно необходимо выполнить. В этом случае метод просто запускает новый поток и конфигурирует его для вызова main метод. Метод также обновляет executing задействованная переменная и генерирует уведомления KVO для isExecuting ключевой путь для отражения изменения в том значении. С его выполненной работой этот метод тогда просто возвращается, оставляя недавно отдельный поток для выполнения фактической задачи.

Перечисление 2-6  метод запуска

- (void)start {
   // Always check for cancellation before launching the task.
   if ([self isCancelled])
   {
      // Must move the operation to the finished state if it is canceled.
      [self willChangeValueForKey:@"isFinished"];
      finished = YES;
      [self didChangeValueForKey:@"isFinished"];
      return;
   }
 
   // If the operation is not canceled, begin executing the task.
   [self willChangeValueForKey:@"isExecuting"];
   [NSThread detachNewThreadSelector:@selector(main) toTarget:self withObject:nil];
   executing = YES;
   [self didChangeValueForKey:@"isExecuting"];
}

Перечисление 2-7 показывает остающуюся реализацию для MyOperation класс. Как был замечен в Перечислении 2-6, main метод является точкой входа для нового потока. Это выполняет работу, связанную с объектом операции, и вызывает пользовательское completeOperation метод, когда наконец выполнена та работа. completeOperation метод тогда генерирует необходимые уведомления KVO для обоих isExecuting и isFinished ключ соединяет каналом для отражения изменения в состоянии работы.

Перечисление 2-7  , Обновляющее работу во время завершения

- (void)main {
   @try {
 
       // Do the main work of the operation here.
 
       [self completeOperation];
   }
   @catch(...) {
      // Do not rethrow exceptions.
   }
}
 
- (void)completeOperation {
    [self willChangeValueForKey:@"isFinished"];
    [self willChangeValueForKey:@"isExecuting"];
 
    executing = NO;
    finished = YES;
 
    [self didChangeValueForKey:@"isExecuting"];
    [self didChangeValueForKey:@"isFinished"];
}

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

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

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

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

Если Вы хотите реализовать поддержку зависимостей от чего-то помимо других объектов операции, можно также переопределить isReady метод и сила это для возврата NO пока Ваши пользовательские зависимости не были удовлетворены. (Если Вы реализуете пользовательские зависимости, убедиться вызвать super от Вашего isReady метод, если Вы все еще поддерживаете систему управления зависимости по умолчанию, предоставленную NSOperation класс.), Когда состояние готовности Ваших изменений объекта операции, генерируйте уведомления KVO для isReady ключевой путь для создания отчетов о тех изменениях. Если Вы не переопределяете addDependency: или removeDependency: методы, Вы не должны должны быть волноваться о генерации уведомлений KVO для dependencies ключевой путь.

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

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

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

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

Конфигурирование зависимостей от взаимодействия

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

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

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

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

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

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

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

Приоритетные уровни не являются заменой для зависимостей. Приоритеты определяют порядок, в котором очередь работы начинает выполнять только те операции, которые в настоящее время готовы. Например, если очередь содержит и высокоприоритетную и низкоприоритетную работу, и обе операции готовы, очередь выполняет высокоприоритетную работу сначала. Однако, если высокоприоритетная работа не готова, но низкоприоритетная работа, очередь выполняет низкоприоритетную работу сначала. Если Вы хотите препятствовать тому, чтобы одна работа запустилась, пока другая работа не закончилась, необходимо использовать зависимости (как описано в Конфигурировании Зависимостей от Взаимодействия) вместо этого.

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

В OS X v10.6 и позже, возможно сконфигурировать приоритет выполнения базового потока работы. Политиками потока в системе самостоятельно управляет ядро, но в общем более высоком приоритете потокам дают больше возможностей работать, чем потоки более низкого приоритета. В объекте операции Вы указываете приоритет потока как значение с плавающей точкой в диапазоне 0.0 к 1,0, с 0,0 являющийся самым низким приоритетом и 1.0 являющийся самым высоким приоритетом. Если Вы не указываете явный приоритет потока, работа работает с приоритетом потока по умолчанию 0,5.

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

Установка блока завершения

Когда его основная задача заканчивает выполняться, в OS X v10.6 и позже, работа может выполнить блок завершения. Можно использовать блок завершения для выполнения любой работы, что Вы не рассматриваете часть основной задачи. Например, Вы могли бы использовать этот блок, чтобы уведомить заинтересованные клиенты, что завершилась сама работа. Параллельный объект операции мог бы использовать этот блок для генерации его заключительных уведомлений KVO.

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

Подсказки для реализации объектов операции

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

Управление памятью в объектах операции

Следующие разделы описывают основные элементы хорошего управления памятью в объекте операции. Для получения общей информации об управлении памятью в программах Objective C, см. Усовершенствованное Руководство по программированию управления памятью.

Избегите хранения на поток

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

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

Сохраните ссылки на свой объект операции по мере необходимости

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

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

Ошибки из-за неправильного обращения и исключения

Поскольку операции являются чрезвычайно дискретными объектами в Вашем приложении, они ответственны за обработку любых возникающих ошибок или исключений. В OS X v10.6 и позже, значение по умолчанию start метод, предоставленный NSOperation класс не ловит исключения. (В OS X v10.5, метод запуска действительно ловит и подавляет исключения.) Ваш собственный код должен всегда ловить и подавлять исключения непосредственно. Это должно также проверить коды ошибки и уведомить надлежащие части Вашего приложения по мере необходимости. И если Вы заменяете start метод, необходимо так же поймать любые исключения в пользовательской реализации, чтобы препятствовать тому, чтобы они оставили объем базового потока.

Среди типов ошибочных ситуаций Вы должны быть подготовлены обработать, следующее:

  • Проверьте и обработайте UNIX errno- разработайте коды ошибки.

  • Проверьте явные коды ошибки, возвращенные методами и функциями.

  • Исключения выгоды, выданные Вашим собственным кодом или другими системными платформами.

  • Исключения выгоды, выданные NSOperation сам класс, выдающий исключения в следующих ситуациях:

    • Когда работа не готова выполниться, но start метод вызывают

    • Когда работа выполняется или законченная (возможно, потому что она была отменена), и start метод вызывают снова

    • Когда Вы пытаетесь добавить блок завершения к работе, уже выполняющейся или законченная

    • Когда Вы пытаетесь получить результат NSInvocationOperation отмененный объект

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

Определение надлежащего объема для объектов операции

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

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

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

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

Выполнение операций

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

Добавление операций очереди работы

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

Для создания очереди Вы выделяете его в своем приложении, поскольку Вы были бы любой другой объект:

NSOperationQueue* aQueue = [[NSOperationQueue alloc] init];

Чтобы добавить операции к очереди, Вы используете addOperation: метод. В OS X v10.6 и позже, можно добавить группы операций с помощью addOperations:waitUntilFinished: метод, или можно добавить, что блок возражает непосредственно против очереди (без соответствующего объекта операции) использованию addOperationWithBlock: метод. Каждый из этих методов стоит в очереди работа (или операции) и уведомляет очередь, что это должно начать обрабатывать их. В большинстве случаев операции выполняются, будучи добавленным к очереди, но очередь работы может задержать выполнение операций с очередями по любой из нескольких причин. В частности выполнение может быть задержано, если операции с очередями зависят от других еще не завершившихся операций. Если сама очередь работы временно отстранена или уже выполняет свое максимальное количество параллельных операций, выполнение может также быть задержано. Следующие примеры показывают базовый синтаксис для добавления операций очереди.

[aQueue addOperation:anOp]; // Add a single operation
[aQueue addOperations:anArrayOfOps waitUntilFinished:NO]; // Add multiple operations
[aQueue addOperationWithBlock:^{
   /* Do something. */
}];

Несмотря на то, что NSOperationQueue класс разработан для параллельного выполнения операций, возможно вынудить единственную очередь выполнить только одну работу за один раз. setMaxConcurrentOperationCount: метод позволяет Вам сконфигурировать максимальное количество параллельных операций для объекта очереди работы. Передача значения 1 к этому методу заставляет очередь выполнять только одну работу за один раз. Несмотря на то, что только одна работа за один раз может выполниться, порядок выполнения все еще на основе других факторов, таких как готовность каждой работы и ее присвоенного приоритета. Таким образом сериализированная очередь работы не предлагает вполне того же поведения, как последовательная очередь отгрузки на Центральной Отгрузке делает. Если порядок выполнения Ваших объектов операции важен для Вас, необходимо использовать зависимости для установления того порядка прежде, чем добавить операции к очереди. Для получения информации о конфигурировании зависимостей посмотрите Зависимости от Взаимодействия Конфигурирования.

Для получения информации об использовании очередей работы посмотрите Ссылку класса NSOperationQueue. Для получения дополнительной информации о последовательных очередях отгрузки, посмотрите Создающие Последовательные Очереди Отгрузки.

Выполнение операций вручную

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

Работу не считают способной работать до isReady возвраты метода YES. isReady метод интегрируется в систему управления зависимости NSOperation класс для обеспечения состояния зависимостей работы. Только то, когда его зависимости очищены, является работой, свободной начать выполняться.

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

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

Перечисление 2-8 показывает простой пример вида проверок, которые необходимо выполнить перед выполняющимися операциями вручную. Если возвращается метод NO, Вы могли запланировать таймер и вызвать метод снова позже. Вы тогда продолжили бы перепланировать таймер до возвратов метода YES, который мог произойти, потому что была отменена работа.

Перечисление 2-8  , Выполняющее объект операции вручную

- (BOOL)performOperation:(NSOperation*)anOp
{
   BOOL        ranIt = NO;
 
   if ([anOp isReady] && ![anOp isCancelled])
   {
      if (![anOp isConcurrent])
         [anOp start];
      else
         [NSThread detachNewThreadSelector:@selector(start)
                   toTarget:anOp withObject:nil];
      ranIt = YES;
   }
   else if ([anOp isCancelled])
   {
      // If it was canceled before it was started,
      //  move the operation to the finished state.
      [self willChangeValueForKey:@"isFinished"];
      [self willChangeValueForKey:@"isExecuting"];
      executing = NO;
      finished = YES;
      [self didChangeValueForKey:@"isExecuting"];
      [self didChangeValueForKey:@"isFinished"];
 
      // Set ranIt to YES to prevent the operation from
      // being passed to this method again in the future.
      ranIt = YES;
   }
   return ranIt;
}

Отмена операций

После того, как добавленный к очереди работы, объект операции эффективно принадлежит очереди и не может быть удален. Единственный способ исключить работу из очереди состоит в том, чтобы отменить его. Можно отменить единственный отдельный объект операции путем вызова cancel метод или Вы можете отменить все объекты операции в очереди путем вызова cancelAllOperations метод объекта очереди.

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

Ожидание операций для окончания

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

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

Приостановка и возобновление очередей

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