Подклассы NSOperation должны быть параллельными и отменяемыми
Я не могу найти хорошую документацию о том, как подкласс NSOperation
быть одновременным, а также поддерживать отмену. Я читаю документы Apple, но не могу найти "официальный" пример.
Вот мой исходный код:
@synthesize isExecuting = _isExecuting;
@synthesize isFinished = _isFinished;
@synthesize isCancelled = _isCancelled;
- (BOOL)isConcurrent
{
return YES;
}
- (void)start
{
/* WHY SHOULD I PUT THIS ?
if (![NSThread isMainThread])
{
[self performSelectorOnMainThread:@selector(start) withObject:nil waitUntilDone:NO];
return;
}
*/
[self willChangeValueForKey:@"isExecuting"];
_isExecuting = YES;
[self didChangeValueForKey:@"isExecuting"];
if (_isCancelled == YES)
{
NSLog(@"** OPERATION CANCELED **");
}
else
{
NSLog(@"Operation started.");
sleep(1);
[self finish];
}
}
- (void)finish
{
NSLog(@"operationfinished.");
[self willChangeValueForKey:@"isExecuting"];
[self willChangeValueForKey:@"isFinished"];
_isExecuting = NO;
_isFinished = YES;
[self didChangeValueForKey:@"isExecuting"];
[self didChangeValueForKey:@"isFinished"];
if (_isCancelled == YES)
{
NSLog(@"** OPERATION CANCELED **");
}
}
В примере, который я нашел, я не понимаю, почему используется executeSelectorOnMainThread: Это помешало бы моей работе работать одновременно.
Кроме того, когда я закомментирую эту строку, мои операции будут выполняться одновременно. Тем не менее isCancelled
флаг не изменен, хотя я звонил cancelAllOperations
,
6 ответов
Итак, насколько я понимаю, у вас есть два вопроса:
Вам нужен
performSelectorOnMainThread:
сегмент, который появляется в комментариях в вашем коде? Что делает этот код?Почему
_isCancelled
флаг не изменяется при звонкеcancelAllOperations
наNSOperationQueue
что содержит эта операция?
Давайте разберемся с этим по порядку. Я собираюсь предположить, что ваш подкласс NSOperation
называется MyOperation
Просто для простоты объяснения. Я объясню, что вы неправильно понимаете, а затем приведу исправленный пример.
1. Запуск NSOperations одновременно
Большую часть времени вы будете использовать NSOperation
с NSOperationQueue
и из вашего кода это звучит так, как будто вы это делаете. В этом случае ваш MyOperation
всегда будет выполняться в фоновом потоке, независимо от того, что -(BOOL)isConcurrent
метод возвращает, так как NSOperationQueue
s явно предназначены для запуска операций в фоновом режиме.
Таким образом, вам обычно не нужно переопределять -[NSOperation start]
метод, так как по умолчанию он просто вызывает -main
метод. Это метод, который вы должны переопределить. По умолчанию -start
метод уже обрабатывает настройку isExecuting
а также isFinished
для вас в подходящее время.
Так что если вы хотите NSOperation
для запуска в фоновом режиме, просто переопределите -main
метод и положить его на NSOperationQueue
,
performSelectorOnMainThread:
в вашем коде будет вызывать каждый экземпляр MyOperation
всегда выполнять свою задачу в главном потоке. Поскольку в потоке одновременно может быть запущен только один фрагмент кода, это означает, что никакой другой MyOperation
с может быть работает. Вся цель NSOperation
а также NSOperationQueue
это сделать что-то в фоновом режиме.
Единственный раз, когда вы хотите навязать что-то в основной поток, это когда вы обновляете пользовательский интерфейс. Если вам нужно обновить интерфейс, когда ваш MyOperation
заканчивается, то есть когда вы должны использовать performSelectorOnMainThread:
, Я покажу, как это сделать, в моем примере ниже.
2. Отмена NSOperation
-[NSOperationQueue cancelAllOperations]
вызывает -[NSOperation cancel]
метод, который вызывает последующие вызовы -[NSOperation isCancelled]
возвращать YES
, Однако вы сделали две вещи, чтобы сделать это неэффективным.
Ты используешь
@synthesize isCancelled
переопределить NSOperation's-isCancelled
метод. Нет причин делать это.NSOperation
уже реализует-isCancelled
в вполне приемлемой манере.Вы проверяете свою собственную
_isCancelled
переменная экземпляра, чтобы определить, была ли операция отменена.NSOperation
гарантирует, что[self isCancelled]
вернусьYES
если операция была отменена. Это не гарантирует, что будет вызван ваш пользовательский метод установки, и что ваша собственная переменная экземпляра не устарела. Вы должны проверять[self isCancelled]
Что вы должны делать
Заголовок:
// MyOperation.h
@interface MyOperation : NSOperation {
}
@end
И реализация:
// MyOperation.m
@implementation MyOperation
- (void)main {
if ([self isCancelled]) {
NSLog(@"** operation cancelled **");
}
// Do some work here
NSLog(@"Working... working....")
if ([self isCancelled]) {
NSLog(@"** operation cancelled **");
}
// Do any clean-up work here...
// If you need to update some UI when the operation is complete, do this:
[self performSelectorOnMainThread:@selector(updateButton) withObject:nil waitUntilDone:NO];
NSLog(@"Operation finished");
}
- (void)updateButton {
// Update the button here
}
@end
Обратите внимание, что вам не нужно ничего делать с isExecuting
, isCancelled
, или же isFinished
, Все это обрабатывается автоматически для вас. Просто переопределить -main
метод. Это так просто.
(Примечание: технически это не "одновременный" NSOperation
, в смысле -[MyOperation isConcurrent]
вернется NO
как реализовано выше. Тем не менее, он будет работать в фоновом потоке. isConcurrent
метод действительно должен быть назван -willCreateOwnThread
, так как это более точное описание намерения метода.)
Отличный ответ @BJHomer заслуживает обновления.
Параллельные операции должны переопределять start
метод вместо main
,
Как указано в документации Apple:
Если вы создаете параллельную операцию, вам необходимо как минимум переопределить следующие методы и свойства:
start
asynchronous
executing
finished
Правильная реализация также требует переопределения cancel
также. Сделать потокобезопасным для подкласса и получить необходимую семантику правильно также довольно сложно.
Таким образом, я поместил полный и рабочий подкласс в качестве предложения, реализованного в Swift в Code Review. Комментарии и предложения приветствуются.
Этот класс может быть легко использован как базовый класс для вашего пользовательского класса операций.
Я знаю, что это старый вопрос, но я изучал его в последнее время, сталкивался с теми же примерами и сомневался.
Если вся ваша работа может выполняться синхронно внутри метода main, вам не нужно выполнять параллельную операцию или переопределять запуск, просто выполняйте свою работу и по окончании возвращайтесь из main.
Однако, если ваша рабочая нагрузка асинхронна по своей природе - т.е. загружает NSURLConnection, вы должны запустить подкласс. Когда ваш метод запуска возвращается, операция еще не закончена. Он будет считаться завершенным NSOperationQueue только тогда, когда вы вручную отправляете уведомления KVO на флаги isFinished и isExecuting (например, после завершения или сбоя асинхронной загрузки URL).
Наконец, может потребоваться отправить запуск в основной поток, когда асинхронная рабочая нагрузка, которую вы хотите запустить, требует прослушивания цикла выполнения в главном потоке. Поскольку сама работа является асинхронной, это не ограничит ваш параллелизм, но запуск работы в рабочем потоке может быть не подготовлен надлежащим циклом выполнения.
Посмотрите на ASIHTTPRequest. Это класс-оболочка HTTP, построенный поверх NSOperation
как подкласс и, кажется, реализовать их. Обратите внимание, что по состоянию на середину 2011 года разработчик рекомендует не использовать ASI для новых проектов.
Что касается определения "отмененного" свойства (или определения "_cancelled" iVAR) внутри подкласса NSOperation, обычно это НЕ обязательно. Просто потому, что когда USER запускает отмену, пользовательский код всегда должен уведомлять наблюдателей KVO о том, что ваша операция завершена. Другими словами, isCancelled => isFinished.
В частности, когда объект NSOperation зависит от завершения других объектов операции, он отслеживает путь ключа isFinished для этих объектов. Если не сгенерировать уведомление о завершении (в случае отмены), это может помешать выполнению других операций в вашем приложении.
Кстати, ответ @BJ Homer: "Метод isConcurrent действительно должен называться -willCreateOwnThread" имеет ОЧЕНЬ смысл!
Потому что, если вы НЕ переопределяете start-метод, просто вручную вызовите NSOperation-Object-default-start-method, сам вызывающий поток по умолчанию является синхронным; Таким образом, NSOperation-Object является только не параллельной операцией.
Однако, если вы переопределите метод start, внутри реализации метода start пользовательский код должен порождать отдельный поток… и т. Д., Тогда вы успешно нарушите ограничение "синхронизация по умолчанию вызывающего потока", в результате чего объект NSOperation-Object станет параллельная операция, после этого она может выполняться асинхронно.
Это сообщение в блоге:
http://www.dribin.org/dave/blog/archives/2009/09/13/snowy_concurrent_operations/
объясняет, почему вам может понадобиться:
if (![NSThread isMainThread])
{
[self performSelectorOnMainThread:@selector(start) withObject:nil waitUntilDone:NO];
return;
}
в вашем start
метод.