Очереди работы
Операции какао являются объектно-ориентированным способом инкапсулировать работу, которую Вы хотите выполнить асинхронно. Операции разработаны, чтобы использоваться или в сочетании с очередью работы или собой. Поскольку они - базируемый Objective C, операции обычно используются в Основанных на какао приложениях в OS X и iOS.
Эта глава показывает Вам, как определить и использовать операции.
Об объектах операции
Объект операции является экземпляром NSOperation
класс (в платформе Основы), что Вы используете для инкапсуляции работы, которую Вы хотите, чтобы Ваше приложение выполнило. NSOperation
сам класс является абстрактным базовым классом, который должен быть разделен на подклассы для выполнения любой полезной работы. Несмотря на то, чтобы быть абстрактным, этот класс действительно обеспечивает существенное количество инфраструктуры для минимизации объема работы, который необходимо сделать в собственных подклассах. Кроме того, платформа Основы обеспечивает два конкретных подкласса, которые можно использовать как есть с существующим кодом. Таблица 2-1 перечисляет эти классы, вместе со сводкой того, как Вы используете каждого.
Класс | Описание |
---|---|
Класс Вы используете как есть для создания объекта операции на основе объекта и селектора из приложения. Можно использовать этот класс в случаях, где у Вас есть существующий метод, уже выполняющий необходимую задачу. Поскольку это не требует разделения на подклассы, можно также использовать этот класс для создания объектов операции более динамическим способом. Для получения информации о том, как использовать этот класс, посмотрите Создание Объекта NSInvocationOperation. | |
Класс Вы используете как есть для выполнения одного или более блочных объектов одновременно. Поскольку это может выполнить больше чем один блок, объект блочной операции управляет использованием семантической группы; только то, когда все связанные блоки закончили выполняться, является работой, которую самой рассматривают законченной. Для получения информации о том, как использовать этот класс, посмотрите Создание Объекта NSBlockOperation. Этот класс доступен в OS X v10.6 и позже. Для получения дополнительной информации о блоках, посмотрите, что Блоки Программируют Темы. | |
Базовый класс для определения пользовательских объектов операции. Разделение на подклассы Для получения информации о том, как определить пользовательские объекты операции, посмотрите Определение Пользовательского Объекта операции. |
Все объекты операции поддерживают следующие главные функции:
Поддержка установления основанных на графике зависимостей между объектами операции. Эти зависимости препятствуют тому, чтобы данная работа работала до всех операций, от которых она зависит, закончили работать. Для получения информации о том, как сконфигурировать зависимости, посмотрите Зависимости от Взаимодействия Конфигурирования.
Поддержка дополнительного блока завершения, выполняющегося после основной задачи работы, заканчивается. (OS X v10.6 и позже только.) Для получения информации о том, как установить блок завершения, посмотрите Установку Блока Завершения.
Поддержка того, чтобы наблюдать изменения к режиму выполнения Ваших операций с помощью уведомлений KVO. Для получения информации о том, как наблюдать уведомления KVO, посмотрите, что Значение ключа Наблюдает Руководство по программированию.
Поддержка приоритезации операций и таким образом влияния на их относительный порядок выполнения. Для получения дополнительной информации посмотрите Изменение Приоритета Выполнения Работы.
Поддержка отмены семантики, которые позволяют Вам останавливать работу, в то время как это выполняется. Для получения информации о том, как отменить операции, посмотрите Операции Отмены. Для получения информации о том, как поддерживать отмену в Ваших собственных операциях, посмотрите События Отмены Ответа.
Операции разработаны, чтобы помочь Вам улучшить уровень параллелизма в Вашем приложении. Операции являются также хорошим способом организовать и инкапсулировать поведение Вашего приложения в простые дискретные блоки. Вместо того, чтобы выполнить некоторый бит кода основного потока Вашего приложения, можно представить один или несколько объектов операции очереди и позволить соответствующей работе выполняться асинхронно на одной или более отдельных потоках.
Параллельный по сравнению с непараллельными операциями
Несмотря на то, что Вы обычно выполняете операции путем добавления их к очереди работы, выполнение так не требуется. Также возможно выполнить объект операции вручную путем вызова 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
Вам нужен пользовательский метод инициализации для помещения объекта операции в известное состояние и пользовательское main
метод для выполнения задачи. Можно реализовать дополнительные методы по мере необходимости, конечно, такие как следующее:
Пользовательские методы, которые Вы планируете вызвать от реализации Вашего
main
методМетоды доступа для установки значений данных и доступа к результатам работы
Методы
NSCoding
протокол, чтобы позволить Вам заархивировать и разархивировать объект операции
Перечисление 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 перечисляет методы, которые Вы обычно переопределяете для реализации параллельной работы.
Метод | Описание |
---|---|
| (Требуемый) Все параллельные операции должны переопределить этот метод и заменить поведение по умолчанию их собственной реализацией. Для выполнения работы вручную Вы вызываете |
| (Необязательно) Этот метод обычно используется для реализации задачи, связанной с объектом операции. Несмотря на то, что Вы могли выполнить задачу в |
(Требуемые) операции Concurrent ответственны за установку их среды выполнения и создание отчетов о состоянии той среды внешним клиентам. Поэтому параллельная работа должна поддержать некоторую информацию состояния для знания, когда это выполняет свою задачу и когда это закончило ту задачу. Это должно тогда сообщить что состояние с помощью этих методов. Ваши реализации этих методов должно быть безопасно вызвать от других потоков одновременно. Необходимо также генерировать надлежащие уведомления KVO для ожидаемых ключевых путей при изменении значений, о которых сообщают эти методы. | |
(Требуемый) идентифицировать работу как параллельную работу, переопределите этот метод и возврат |
Остальная часть этого раздела показывает демонстрационную реализацию 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:
метод. Приостановка очереди уже не заставляет выполняющиеся операции приостанавливаться посреди их задач. Это просто препятствует тому, чтобы новые операции были запланированы для выполнения. Вы могли бы временно отстранить очередь в ответ на пользовательский запрос для приостановки любой продолжающейся работы, потому что ожидание состоит в том, что пользователь мог бы в конечном счете хотеть возобновить ту работу.