Асинхронные методы в NSOperation
Я извлекаю некоторые данные из Facebook Connect (используя фреймворк FBConnect Objective-C 2.0), и я делаю все это в NSOperation. Он находится в NSOperation, потому что у меня есть еще несколько других операций, которые также выполняются, и это один из них.
Проблема в том, что все вызовы FBConnect асинхронны. Из-за этого основной метод NSOperation быстро заканчивается, и операция отмечена как завершенная.
Есть ли способ преодолеть это? Казалось бы, в FBConnect нет синхронных опций!
Большое спасибо,
Mike
Ответы
Ответ 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