Подклассы 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 ответов

Решение

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

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

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

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

1. Запуск NSOperations одновременно

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

Таким образом, вам обычно не нужно переопределять -[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 переопределить NSOperation's -isCancelled метод. Нет причин делать это. 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, так как это более точное описание намерения метода.)

Отличный ответ @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 метод.

Другие вопросы по тегам