Подклассы NSOperation должны быть параллельными и отменяемыми - PullRequest
49 голосов
/ 05 октября 2010

Я не могу найти хорошую документацию о том, как подкласс 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 **");
    }
}

В найденном примере я не понимаюпочему выполняетсяSelectorOnMainThread: используется.Это предотвратит одновременное выполнение моей операции.

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

Ответы [ 6 ]

109 голосов
/ 29 ноября 2010

Хорошо, насколько я понимаю, у вас есть два вопроса:

  1. Вам нужен сегмент performSelectorOnMainThread:, который появляется в комментариях в вашем коде? Что делает этот код?

  2. Почему флаг _isCancelled не изменяется при вызове cancelAllOperations на NSOperationQueue, который содержит эту операцию?

Давайте разберемся с этим по порядку. Я собираюсь предположить, что ваш подкласс NSOperation называется MyOperation, просто для простоты объяснения. Я объясню, что вы неправильно понимаете, а затем приведу исправленный пример.

1. Одновременный запуск NSOperations

В большинстве случаев вы будете использовать NSOperation s с NSOperationQueue, и из вашего кода это звучит так, как будто вы это делаете. В этом случае ваш MyOperation всегда будет выполняться в фоновом потоке, независимо от того, что возвращает метод -(BOOL)isConcurrent, поскольку NSOperationQueue явно предназначены для выполнения операций в фоновом режиме.

Таким образом, вам, как правило, нет необходимости переопределять метод -[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. Однако , вы сделали две вещи, чтобы сделать это неэффективным.

  1. Вы используете @synthesize isCancelled для переопределения метода -isCancelled NSOperation. Нет причин делать это. NSOperation уже реализует -isCancelled вполне приемлемым образом.

  2. Вы проверяете собственную переменную экземпляра _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, так как это более точное описание намерения метода.)

4 голосов
/ 15 октября 2015

Отличный ответ @BJHomer заслуживает обновления.

Параллельные операции должны переопределять метод start вместо main.

Как указано в документации Apple :

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

  • start
  • asynchronous
  • executing
  • finished

Правильная реализация также требует для переопределения cancel. Создание подкласса поточно-ориентированного и получение необходимой семантики также довольно сложно.

Таким образом, я поместил полный и рабочий подкласс в качестве предложения, реализованного в Swift в Code Review. Комментарии и предложения приветствуются.

Этот класс может быть легко использован в качестве базового класса для пользовательского класса операций.

2 голосов
/ 18 октября 2013

Я знаю, что это старый вопрос, но я изучал его в последнее время, сталкивался с такими же примерами и имел те же сомнения.

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

Однако, если ваша рабочая нагрузка асинхронна по своей природе - т.е. при загрузке NSURLConnection, вы должны запустить подкласс. Когда ваш метод запуска возвращается, операция еще не закончена. NSOperationQueue будет считаться завершенным только при ручной отправке уведомлений KVO на флаги isFinished и isExecuting (например, после завершения или сбоя асинхронной загрузки URL).

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

0 голосов
/ 13 июля 2014

Относительно определения свойства " отменено " (или определения " _cancelled " iVAR) внутри подкласса NSOperation, как правило, это НЕ обязательно. Просто потому, что когда USER запускает отмену, пользовательский код всегда должен уведомлять наблюдателей KVO, что ваша операция теперь завершена с ее работой. Другими словами, isCancelled => isFinished.

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


Кстати, ответ @BJ Homer: "Метод isConcurrent действительно должен быть с именем -willCreateOwnThread" имеет ОЧЕНЬ смысл !

Поскольку, если вы НЕ переопределяете метод запуска, просто вручную вызовите метод запуска по умолчанию NSOperation-Object, сам вызывающий поток по умолчанию является синхронным; Таким образом, NSOperation-Object является только не параллельной операцией.

Однако, если вы переопределите метод запуска, внутри реализации метода запуска пользовательский код должен создать отдельный поток … и т. Д., Тогда вы успешно нарушите ограничение "синхронизация по умолчанию вызывающего потока" ", поэтому NSOperation-Object становится параллельной операцией, впоследствии он может выполняться асинхронно.

0 голосов
/ 02 октября 2012

Это сообщение в блоге:

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 методе.

0 голосов
/ 29 ноября 2010

Взгляните на ASIHTTPRequest . Это класс-оболочка HTTP, построенный поверх NSOperation в качестве подкласса и, похоже, реализующий их. Обратите внимание, что с середины 2011 года разработчик рекомендует не использовать ASI для новых проектов.

...