Изучение NSBlockOperation
Я большой поклонник блоков, но не использовал их для параллелизма. После некоторого поиска в Google, я собрал эту идею, чтобы спрятать все, что я узнал, в одном месте. Цель состоит в том, чтобы выполнить блок в фоновом режиме, а когда он закончится, выполнить другой блок (например, анимацию UIView)...
- (NSOperation *)executeBlock:(void (^)(void))block completion:(void (^)(BOOL finished))completion {
NSOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:block];
NSOperation *completionOperation = [NSBlockOperation blockOperationWithBlock:^{
completion(blockOperation.isFinished);
}];
[completionOperation addDependency:blockOperation];
[[NSOperationQueue mainQueue] addOperation:completionOperation];
NSOperationQueue *backgroundOperationQueue = [[NSOperationQueue alloc] init];
[backgroundOperationQueue addOperation:blockOperation];
return blockOperation;
}
- (void)testIt {
NSMutableString *string = [NSMutableString stringWithString:@"tea"];
NSString *otherString = @"for";
NSOperation *operation = [self executeBlock:^{
NSString *yetAnother = @"two";
[string appendFormat:@" %@ %@", otherString, yetAnother];
} completion:^(BOOL finished) {
// this logs "tea for two"
NSLog(@"%@", string);
}];
NSLog(@"keep this operation so we can cancel it: %@", operation);
}
Мои вопросы:
- Это работает, когда я запускаю его, но я что-то упустил... скрытая земля? Я не проверял отмену (потому что я не изобрел длинную операцию), но похоже, что это будет работать?
- Я обеспокоен тем, что мне нужно квалифицировать свое объявление backgroundOperation, чтобы я мог ссылаться на него в блоке завершения. Компилятор не жалуется, но существует ли там цикл сохранения?
- Если бы "строка" была иваром, что бы произошло, если бы я наблюдал это по значению ключа во время работы блока? Или настроить таймер на основном потоке и периодически его регистрировать? Смогу ли я увидеть прогресс? Я объявил бы это атомным?
- Если это работает так, как я ожидаю, то это хороший способ скрыть все детали и получить параллелизм. Почему Apple не написал это для меня? Я что-то упустил?
Благодарю.
3 ответа
Я не эксперт в NSOperation или NSOperationQueues, но я думаю, что приведенный ниже код немного лучше, хотя я думаю, что у него все еще есть некоторые предостережения. Вероятно, достаточно для некоторых целей, но не является общим решением для параллелизма:
- (NSOperation *)executeBlock:(void (^)(void))block
inQueue:(NSOperationQueue *)queue
completion:(void (^)(BOOL finished))completion
{
NSOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:block];
NSOperation *completionOperation = [NSBlockOperation blockOperationWithBlock:^{
completion(blockOperation.isFinished);
}];
[completionOperation addDependency:blockOperation];
[[NSOperationQueue currentQueue] addOperation:completionOperation];
[queue addOperation:blockOperation];
return blockOperation;
}
Теперь давайте использовать это:
- (void)tryIt
{
// Create and configure the queue to enqueue your operations
backgroundOperationQueue = [[NSOperationQueue alloc] init];
// Prepare needed data to use in the operation
NSMutableString *string = [NSMutableString stringWithString:@"tea"];
NSString *otherString = @"for";
// Create and enqueue an operation using the previous method
NSOperation *operation = [self executeBlock:^{
NSString *yetAnother = @"two";
[string appendFormat:@" %@ %@", otherString, yetAnother];
}
inQueue:backgroundOperationQueue
completion:^(BOOL finished) {
// this logs "tea for two"
NSLog(@"%@", string);
}];
// Keep the operation for later uses
// Later uses include cancellation ...
[operation cancel];
}
Некоторые ответы на ваши вопросы:
Отмена Обычно вы подкласс NSOperation, чтобы вы могли проверить
self.isCancelled
и вернуться раньше. Посмотрите эту ветку, это хороший пример. В текущем примере вы не можете проверить, была ли операция отменена из блока, который вы предоставляете для созданияNSBlockOperation
потому что в то время такой операции еще не было. отменаNSBlockOperation
То, что блок вызывается, по-видимому, возможно, но громоздко.NSBlockOperation
s для конкретных простых случаев. Если вам нужно отменить, вы лучше подклассNSOperation
:)Я не вижу здесь проблемы. Хотя обратите внимание на две вещи. a) Я изменил метод do для запуска блока завершения в текущей очереди b) в качестве параметра требуется очередь. Как сказал @Mike Weller, вам лучше поставить
background queue
так что вам не нужно создавать по одной на каждую операцию, и вы можете выбрать, какую очередь использовать для запуска ваших материалов:)Я думаю, что да, вы должны сделать
string
atomic
, Одна вещь, которую вы не должны забывать, это то, что если вы поставите в очередь несколько операций, они могут выполняться не в этом порядке (обязательно), так что вы можете получить очень странное сообщение в вашейstring
, Если вам нужно запускать одну операцию одновременно, вы можете сделать:[backgroundOperation setMaxConcurrentOperationCount:1];
прежде чем начать ставить в очередь ваши операции. В документах есть примечание, достойное чтения:Дополнительные поведения очереди операций Очередь операций выполняет свои объекты операций в очереди в зависимости от их приоритета и готовности. Если все объекты в очереди имеют одинаковый приоритет и готовы к выполнению, когда они помещены в очередь, то есть их метод isReady возвращает YES, они выполняются в том порядке, в котором они были отправлены в очередь. Для очереди, максимальное число одновременных операций которой равно 1, это соответствует последовательной очереди. Однако никогда не следует полагаться на последовательное выполнение объектов операций. Изменения в готовности операции могут изменить результирующий порядок выполнения.
Я думаю, что после прочтения этих строк вы знаете:)
Вы не должны создавать новый NSOperationQueue
для каждого executeBlock:completion:
вызов. Это дорого, и пользователь этого API не может контролировать, сколько операций может выполняться одновременно.
Если вы возвращаетесь NSOperation
После этого вы должны предоставить вызывающей стороне решать, в какую очередь их добавлять. Но в этот момент ваш метод на самом деле не делает ничего полезного, и вызывающая сторона может также создать NSBlockOperation
самих себя.
Если вам нужен простой и легкий способ раскрутить блок в фоновом режиме и выполнить код после его завершения, вам, вероятно, лучше сделать несколько простых вызовов GCD с помощью dispatch_*
функции. Например:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// do your background work
// ...
// now execute the 'completion' code.
// you often want to dispatch back to the main thread to update the UI
// For example:
dispatch_async(dispatch_get_main_queue(), ^{
// update UI, etc.
myLabel.text = @"Finished";
});
});
Нет необходимости настраивать блок для запуска по завершении и добавлять зависимости, подобные этой. NSBlockOperation
как все NSOperation
подклассы уже имеет completionBlock
свойство, которое будет автоматически запускаться после завершения работы блока:
@property(copy) void (^completionBlock)(void);
Блок завершения запускается, когда его блок перемещается в finished
государство.