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

Асинхронные методы в NSOperation

Я извлекаю некоторые данные из Facebook Connect (используя фреймворк FBConnect Objective-C 2.0), и я делаю все это в NSOperation. Он находится в NSOperation, потому что у меня есть еще несколько других операций, которые также выполняются, и это один из них.

Проблема в том, что все вызовы FBConnect асинхронны. Из-за этого основной метод NSOperation быстро заканчивается, и операция отмечена как завершенная.

Есть ли способ преодолеть это? Казалось бы, в FBConnect нет синхронных опций!

Большое спасибо,

Mike

4b9b3361

Ответ 1

Ниже приведен полный пример. В вашем подклассе, после завершения асинхронного метода, вызовите [self completeOperation] для перехода в законченное состояние.

@interface AsynchronousOperation()
// 'executing' and 'finished' exist in NSOperation, but are readonly
@property (atomic, assign) BOOL _executing;
@property (atomic, assign) BOOL _finished;
@end

@implementation AsynchronousOperation

- (void) start;
{
    if ([self isCancelled])
    {
        // Move the operation to the finished state if it is canceled.
        [self willChangeValueForKey:@"isFinished"];
        self._finished = YES;
        [self didChangeValueForKey:@"isFinished"];
        return;
    }

    // If the operation is not canceled, begin executing the task.
    [self willChangeValueForKey:@"isExecuting"];
    [NSThread detachNewThreadSelector:@selector(main) toTarget:self withObject:nil];
    self._executing = YES;
    [self didChangeValueForKey:@"isExecuting"];

}

- (void) main;
{
    if ([self isCancelled]) {
        return;
    }

}

- (BOOL) isAsynchronous;
{
    return YES;
}

- (BOOL)isExecuting {
    return self._executing;
}

- (BOOL)isFinished {
    return self._finished;
}

- (void)completeOperation {
    [self willChangeValueForKey:@"isFinished"];
    [self willChangeValueForKey:@"isExecuting"];

    self._executing = NO;
    self._finished = YES;

    [self didChangeValueForKey:@"isExecuting"];
    [self didChangeValueForKey:@"isFinished"];
}

@end

Ответ 2

поместите ваши вызовы FBConnect в 'start', а не 'main' и управляйте свойствами 'isFinished' 'isExecuting. (и верните YES для 'isConcurrent')

Подробнее см. документацию Apple при написании одновременных NSOperations.

Ответ 3

Пожалуйста, поймите это, если ничего другого: в поведении NSOperation нет ничего волшебного. NSOperationQueue просто использует Key Value Observation для мониторинга операций. Единственная причина, почему это не так уж легко, заключается в том, что используемые ключи не совпадают с тем, что в соглашениях Objective-C 2.0 сказано, что они должны быть, поэтому стандартные синтезаторы не будут работать.

В результате, когда вы определяете свой подкласс NSOperation, вы должны предоставить asynchronous, executing и finished. И эти два последних нуждаются в небольшой помощи с вашей стороны, чтобы работать должным образом.

Звучит сложно? Это не просто детали. Каждый шаг на этом пути прост и имеет смысл, но на самом деле он не сработает, пока вы не сделаете все правильно.

Сначала заголовок:

//
//  MyOperation.h

#import <Foundation/Foundation.h>

@interface MyOperation : NSOperation

@property(readonly, getter=isAsynchronous) BOOL asynchronous;
@property(readonly, getter=isExecuting) BOOL executing;
@property(readonly, getter=isFinished) BOOL finished;

@end

Вы, конечно, можете определить executing и finished как readwrite здесь, так что вам не нужно переопределять их как readwrite в реализации. Но мне нравится знать, что только мои операции могут изменить их состояние.

Теперь реализация. Здесь несколько шагов:

  • переопределить finished и executing свойства как чтение/запись.
  • полностью обеспечить реализацию executing и finished это вручную обеспечивает правильные Кво сообщений (так isExecuting, setExecuting:, isFinished и setFinished:).
  • обеспечить хранилище для executing и finished иваров с помощью @synthesize.
  • обеспечить реализацию asynchronous

(Обратите внимание, что этот код, вероятно, будет немного прокручиваться.)

//
//  MyOperation.m

#import "MyOperation.h"

@interface MyOperation()
@property(readwrite) BOOL executing;
@property(readwrite) BOOL finished;
@end

@implementation MyOperation

// Provide your own start.

- (void)start {
    if (self.cancelled) {
        self.finished = YES;
        return;
    }
    NSLog(@"Starting %@", self);
    self.executing = YES;
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5.0 * NSEC_PER_SEC)), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
        NSLog(@"Finished %@", self);
        self.executing = NO;
        self.finished = YES;
    });
}

// The rest of this is boilerplate.

- (BOOL)isAsynchronous {
    return YES;
}

@synthesize executing = _executing;

- (BOOL)isExecuting {
    @synchronized(self) {
        return _executing;
    }
}

- (void)setExecuting:(BOOL)executing {
    @synchronized(self) {
        if (executing != _executing) {
            [self willChangeValueForKey:@"isExecuting"];
            _executing = executing;
            [self didChangeValueForKey:@"isExecuting"];
        }
    }
}

@synthesize finished = _finished;

- (BOOL)isFinished {
    @synchronized(self) {
        return _finished;
    }
}

- (void)setFinished:(BOOL)finished {
    @synchronized(self) {
        if (finished != _finished) {
            [self willChangeValueForKey:@"isFinished"];
            _finished = finished;
            [self didChangeValueForKey:@"isFinished"];
        }
    }
}


@end

Не обязательно проверять (например) executing != _executing в executing != _executing. Правильное поведение обеспечивается автоматически путем вызова willChangeValueForKey, слепого изменения значения, а затем вызова didChangeValueForKey. Но условие означает, что вы можете поставить точку останова в назначении и останавливаться только при изменении значения, и я обнаружил, что это невероятно полезно для отладки моих операций на практике.

Я также видел, как это реализовано путем предоставления настраиваемого состояния поверх executing и finished свойств. Конечно, это прекрасно работает и в некотором смысле лучше… но также требует большего знания KVO, чем в этом примере, и этого уже достаточно.

Наконец, обратите внимание, что я не добавил поддержку отмены после запуска операции. Чтобы сделать это, вы должны отменить cancel (или, может быть, более правильно, наблюдать значение isCancelled) и обработать его. Это сильно усложнит мой простой start пример.

Я запустил этот код в консольном приложении командной строки, добавив 15 операций в очередь с maxConcurrentOperationCount 5, а затем ожидание в очереди, чтобы завершить использование waitUntilAllOperationsAreFinished (поэтому я использовал фоновую очередь для dispatch_after в моем start). Это вывод:

2019-01-22 13:29:32.897893-0800 test[86762:4812871] Starting <MyOperation: 0x10058d2d0>
2019-01-22 13:29:32.897893-0800 test[86762:4812872] Starting <MyOperation: 0x10058d710>
2019-01-22 13:29:32.897903-0800 test[86762:4812873] Starting <MyOperation: 0x100589930>
2019-01-22 13:29:32.898161-0800 test[86762:4812871] Starting <MyOperation: 0x10058edc0>
2019-01-22 13:29:32.898166-0800 test[86762:4812873] Starting <MyOperation: 0x10058ed50>
2019-01-22 13:29:37.898487-0800 test[86762:4812872] Finished <MyOperation: 0x100589930>
2019-01-22 13:29:37.898489-0800 test[86762:4812870] Finished <MyOperation: 0x10058ed50>
2019-01-22 13:29:37.898548-0800 test[86762:4812874] Finished <MyOperation: 0x10058edc0>
2019-01-22 13:29:37.898797-0800 test[86762:4812870] Starting <MyOperation: 0x100590000>
2019-01-22 13:29:37.899160-0800 test[86762:4812870] Finished <MyOperation: 0x10058d710>
2019-01-22 13:29:37.899651-0800 test[86762:4812870] Starting <MyOperation: 0x1005901a0>
2019-01-22 13:29:37.899933-0800 test[86762:4812874] Starting <MyOperation: 0x100590340>
2019-01-22 13:29:37.900133-0800 test[86762:4812871] Finished <MyOperation: 0x10058d2d0>
2019-01-22 13:29:37.900504-0800 test[86762:4812871] Starting <MyOperation: 0x100590680>
2019-01-22 13:29:37.900583-0800 test[86762:4812874] Starting <MyOperation: 0x1005904e0>
2019-01-22 13:29:42.899325-0800 test[86762:4812871] Finished <MyOperation: 0x100590000>
2019-01-22 13:29:42.899541-0800 test[86762:4812874] Starting <MyOperation: 0x100590820>
2019-01-22 13:29:43.393291-0800 test[86762:4812871] Finished <MyOperation: 0x1005901a0>
2019-01-22 13:29:43.393298-0800 test[86762:4812874] Finished <MyOperation: 0x100590340>
2019-01-22 13:29:43.394531-0800 test[86762:4812874] Finished <MyOperation: 0x1005904e0>
2019-01-22 13:29:43.395380-0800 test[86762:4812874] Finished <MyOperation: 0x100590680>
2019-01-22 13:29:43.396359-0800 test[86762:4812874] Starting <MyOperation: 0x1005909c0>
2019-01-22 13:29:43.397440-0800 test[86762:4812872] Starting <MyOperation: 0x100590b60>
2019-01-22 13:29:43.397891-0800 test[86762:4812874] Starting <MyOperation: 0x100590d00>
2019-01-22 13:29:43.399711-0800 test[86762:4812872] Starting <MyOperation: 0x100590ea0>
2019-01-22 13:29:47.900058-0800 test[86762:4812984] Finished <MyOperation: 0x100590820>
2019-01-22 13:29:48.892953-0800 test[86762:4812872] Finished <MyOperation: 0x100590d00>
2019-01-22 13:29:48.892970-0800 test[86762:4812871] Finished <MyOperation: 0x100590b60>
2019-01-22 13:29:48.893019-0800 test[86762:4813163] Finished <MyOperation: 0x100590ea0>
2019-01-22 13:29:48.893562-0800 test[86762:4812984] Finished <MyOperation: 0x1005909c0>
Program ended with exit code: 0