Подтвердить что ты не робот

Подклассификация 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 **");
    }
}

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

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

4b9b3361

Ответ 1

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

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

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

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

1. Одновременное выполнение NSOperations

В большинстве случаев вы будете использовать NSOperation с 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. Однако, вы сделали две вещи, чтобы сделать это неэффективным.

  • Вы используете @synthesize isCancelled для переопределения метода NSOperation -isCancelled. Для этого нет оснований. NSOperation уже реализует -isCancelled совершенно приемлемым образом.

  • Вы проверяете собственную переменную экземпляра _isCancelled, чтобы определить, была ли операция отменена. NSOperation гарантирует, что [self isCancelled] вернет YES, если операция была отменена. Он не гарантирует, что будет вызываться ваш настраиваемый метод setter, или что ваша собственная переменная экземпляра обновлена. Вы должны проверить [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, поскольку это более точное описание намерения метода.)

Ответ 2

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

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

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

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

Ответ 3

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

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

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

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

  • start
  • asynchronous
  • executing
  • finished

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

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

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

Ответ 4

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

Ответ 5

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

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


BTW, @BJ Гомер отвечает: "Метод isConcurrent действительно должен быть с именем -willCreateOwnThread" делает LOT-смысл!

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

Однако, если вы переопределяете метод start, внутри реализации метода запуска, пользовательский код должен порождать отдельный поток... и т.д., то вы успешно нарушаете ограничение "call-thread default, синхронный", поэтому NSOperation-Object становится параллельной операцией, он может запускаться асинхронно после этого.