Подclassификация NSOperation будет одновременной и отменой
Я не могу найти хорошую документацию о том, как подclass 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 **"); } }
В примере, который я нашел, я не понимаю, почему используется useSelectorOnMainThread:. Это предотвратит запуск моей операции одновременно.
- Как создать глобальные функции в Objective-C
- программный доступ к кнопкам громкости iPhone
- Управление памятью в Objective-C
- iPhone: как получить символ местной валюты (т. е. «$» отменить «AU $»)
- Самый эффективный способ создания сетей на Mac / iPhone?
Кроме того, когда я прокомментирую эту строку, я запускаю свои операции одновременно. Однако флаг isCancelled
не изменяется, хотя я вызвал cancelAllOperations
.
- Интерфейс пользовательского интерфейса iOS 7
- Как определить глобальную переменную, доступ к которой можно получить в любом месте приложения?
- iTunes Connect API
- Как заполнить фоновое изображение UIView
- Изменение размера текстового поля UISearchBar?
- Что именно делает делегат в проекте xcode ios?
- iPhone 5 CSS-медиа-запрос
- Перехват / захват iPhone Touch Events для MKMapView
Хорошо, так как я это понимаю, у вас есть два вопроса:
-
Вам нужен сегмент
performSelectorOnMainThread:
который появляется в комментариях в вашем коде? Что делает этот код? -
Почему флаг
_isCancelled
не изменяется, когда вы вызываетеcancelAllOperations
вNSOperationQueue
который содержит эту операцию?
Давайте рассмотрим их по порядку. Я собираюсь предположить, что ваш подclass NSOperation
называется MyOperation
, просто для удобства объяснения. Я объясню, что вы недопонимаете, а затем дайте исправленный пример.
1. Выполнение NSOperations одновременно
В большинстве случаев вы будете использовать NSOperation
с NSOperationQueue
, и из вашего кода это похоже на то, что вы делаете. В этом случае ваш MyOperation
будет всегда запускаться в фоновом streamе, независимо от того, что возвращает метод -(BOOL)isConcurrent
, поскольку NSOperationQueue
s явно предназначен для выполнения операций в фоновом режиме.
Как правило, вам вообще не нужно переопределять метод -[NSOperation start]
, поскольку по умолчанию он просто вызывает метод -main
. Это метод, который вы должны переопределить. Метод -start
умолчанию уже обрабатывает параметр isExecuting
и isFinished
для вас в соответствующее время.
Поэтому, если вы хотите, чтобы NSOperation
выполнялся в фоновом режиме, просто переопределите метод NSOperationQueue
и поместите его в NSOperationQueue
.
Функция performSelectorOnMainThread:
в вашем коде каждый экземпляр MyOperation
всегда будет выполнять свою задачу в основном streamе. Так как только один кусок кода может работать по streamу за раз, это означает, что никакие другие MyOperation
s не могут быть запущены. Вся цель NSOperation
и NSOperationQueue
– сделать что-то в фоновом режиме.
Единственный момент, когда вы хотите заставить вещи на основной stream, – это когда вы обновляете пользовательский интерфейс. Если вам нужно обновить пользовательский интерфейс, когда заканчивается MyOperation
, то есть, когда вы должны использовать performSelectorOnMainThread:
Я покажу, как это сделать в моем примере ниже.
2. Отмена NSOperation
-[NSOperationQueue cancelAllOperations]
вызывает -[NSOperation cancel]
, который вызывает последующие вызовы -[NSOperation isCancelled]
возвращает YES
. Однако вы сделали две вещи, чтобы сделать это неэффективным.
-
Вы используете
@synthesize isCancelled
для переопределения метода-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
как было реализовано выше, но будет запущено в фоновом streamе. Метод isConcurrent
действительно должен быть назван -willCreateOwnThread
, поскольку это более точное описание намерения метода.)
Я знаю, что это старый вопрос, но я изучал это в последнее время и встречал одни и те же примеры и имел те же сомнения.
Если вся ваша работа может выполняться синхронно внутри основного метода, вам не нужна параллельная операция, не отступающая от запуска, просто выполняйте свою работу и возвращайтесь из основного, когда это будет сделано.
Однако, если ваша нагрузка является асинхронной по своей природе, то есть загружая NSURLConnection, вы должны запустить подclass. Когда ваш метод запуска возвращается, операция еще не закончена. Он будет считаться завершенным NSOperationQueue, когда вы вручную отправляете уведомления KVO на флаги isFinished и isExecuting (например, после того, как загрузка URL-адреса async закончится или завершится с ошибкой).
Наконец, можно захотеть отправить начало основному streamу, когда для рабочей нагрузки async, которую вы хотите запустить, требуется прослушивание цикла запуска в основном streamе. Поскольку сама работа является асинхронной, она не будет ограничивать ваш параллелизм, но запуск работы в рабочем streamе может не иметь надлежащей runloop.
Отличный ответ @BJHomer заслуживает обновления.
Параллельные операции должны переопределять метод start
вместо main
.
Как указано в документации Apple :
Если вы создаете параллельную операцию, вам необходимо переопределить следующие методы и свойства как минимум:
-
start
-
asynchronous
-
executing
-
finished
Для правильной реализации также требуется также отменить cancel
. Создание подclassа поточно-безопасным и правильное получение семантики также довольно сложно.
Таким образом, я поставил полный и рабочий подclass в качестве предложения, реализованного в Swift в обзоре кода. Комментарии и предложение приветствуются.
Этот class можно легко использовать в качестве базового classа для вашего пользовательского classа операций.
Взгляните на ASIHTTPRequest . Это class оболочки HTTP, построенный поверх NSOperation
в качестве подclassа и, похоже, реализует их. Обратите внимание, что по состоянию на середину 2011 года разработчик рекомендует не использовать ASI для новых проектов.
Что касается определения « отмененного » свойства (или определения « _cancelled » iVAR) в подclassе NSOperation, обычно это НЕ необходимо. Просто потому, что, когда USER запускает отмену, пользовательский код должен всегда уведомлять наблюдателей KVO о том, что ваша операция завершена с его работой. Другими словами, isCancelled => isFinished.
В частности, когда объект NSOperation зависит от завершения других рабочих объектов, он отслеживает путь ключа isFinished для этих объектов. Несоблюдение уведомления о завершении ( в случае отмены ) может поэтому предотвратить выполнение других операций в вашем приложении.
BTW, @BJ Ответ Гомера: «Метод isConcurrent действительно должен быть назван -willCreateOwnThread» делает LOT-смысл !
Поскольку, если вы НЕ переопределяете метод start, просто вручную вызовите метод NSOperation-Object по умолчанию-start-метод, сам вызов-stream по умолчанию является синхронным; поэтому NSOperation-Object – это только неконкурентная операция.
Однако, если вы переопределяете метод start, внутри реализации метода запуска, пользовательский код должен порождать отдельный stream … и т. Д., Тогда вы успешно нарушаете ограничение «синхронный stream вызовов», поэтому 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
.