NSOperation等待异步块执行

时间:2014-11-12 14:54:42

标签: cocoa asynchronous nsoperation nsoperationqueue

我需要将异步操作放入操作队列,但是,它们需要在另一个

之后执行
self.operationQueue = [NSOperationQueue new];
self.operationQueue.maxConcurrentOperationCount = 1;

[self.operationQueue addOperationWithBlock:^{

    // this is asynchronous
    [peripheral1 connectWithCompletion:^(NSError *error) {

    }];

}];

[self.operationQueue addOperationWithBlock:^{

    // this is asynchronous
    [peripheral2 connectWithCompletion:^(NSError *error) {

    }];

}];

问题是,由于peripheralN connectWithCompletion是异步的,队列中的操作结束而下一个执行,我需要模拟,peripheralN connectWithCompletion是同步的并等待操作结束,直到异步块执行< / p>

所以我需要这样的行为,只使用操作队列

    [peripheral1 connectWithCompletion:^(NSError *error) {

            [peripheral2 connectWithCompletion:^(NSError *error) {

            }];

    }];

3 个答案:

答案 0 :(得分:22)

NSBlockOperation无法处理异步操作,但创建NSOperation的子类并不是那么难......

基本上,您需要创建一个NSOperation,其中包含一个将另一个块作为完成处理程序的块。该块可以这样定义:

typedef void(^AsyncBlock)(dispatch_block_t completionHandler);

然后,在您的NSOperation子类的start方法中,您需要调用AsyncBlock传递dispatch_block_t,它将在执行完毕后调用。您还需要确保KVO符合NSOperation的{​​{1}}和isFinished属性(至少)(请参阅isExecuting NSOperation } docs);这是允许NSOperationQueue等待异步操作完成的原因。

这样的事情:

- (void)start {
    [self willChangeValueForKey:@"isExecuting"];
    _executing = YES;
    [self didChangeValueForKey:@"isExecuting"];

    self.block(^{
        [self willChangeValueForKey:@"isExecuting"];
        _executing = NO;
        [self didChangeValueForKey:@"isExecuting"];
        [self willChangeValueForKey:@"isFinished"];
        _finished = YES;
        [self didChangeValueForKey:@"isFinished"];
    });
}

请注意,_executing_finished需要在您的子类中定义,并且您需要覆盖isExecutingisFinished属性才能返回正确的值。

如果你将所有这些放在一起,以及一个带有AsyncBlock的初始化程序,那么你可以像下面这样将你的操作添加到队列中:

[self.operationQueue addOperationWithBlock:^(dispatch_block_t completionHandler) {
    [peripheral1 connectWithCompletion:^(NSError *error) {
        // call completionHandler when the operation is done
        completionHandler();
    }];
}];

[self.operationQueue addOperationWithBlock:^(dispatch_block_t completionHandler) {
    [peripheral2 connectWithCompletion:^(NSError *error) {
        // call completionHandler when the operation is done
        completionHandler();
    }];
}];

我在这里汇总了一个简单版本的要点:KVO-Compliant Properties。 它只是一个最小的实现,但它应该工作(例如,如果isCancelled也实现的话会很好。

此处复制完整性:

<强> AsyncBlockOperation.h:

#import <Foundation/Foundation.h>

typedef void(^AsyncBlock)(dispatch_block_t completionHandler);

@interface AsyncBlockOperation : NSOperation

@property (nonatomic, readonly, copy) AsyncBlock block;

+ (instancetype)asyncBlockOperationWithBlock:(AsyncBlock)block;

- (instancetype)initWithAsyncBlock:(AsyncBlock)block;

@end


@interface NSOperationQueue (AsyncBlockOperation)

- (void)addAsyncOperationWithBlock:(AsyncBlock)block;

@end

<强> AsyncBlockOperation.m:

#import "AsyncBlockOperation.h"

@interface AsyncBlockOperation () {
    BOOL _finished;
    BOOL _executing;
}

@property (nonatomic, copy) AsyncBlock block;

@end


@implementation AsyncBlockOperation

+ (instancetype)asyncBlockOperationWithBlock:(AsyncBlock)block {
    return [[AsyncBlockOperation alloc] initWithAsyncBlock:block];
}

- (instancetype)initWithAsyncBlock:(AsyncBlock)block {
    if (self = [super init]) {
        self.block = block;
    }
    return self;
}

- (void)start {
    [self willChangeValueForKey:@"isExecuting"];
    _executing = YES;
    [self didChangeValueForKey:@"isExecuting"];

    self.block(^{
        [self willChangeValueForKey:@"isExecuting"];
        _executing = NO;
        [self didChangeValueForKey:@"isExecuting"];
        [self willChangeValueForKey:@"isFinished"];
        _finished = YES;
        [self didChangeValueForKey:@"isFinished"];
    });
}

- (BOOL)isFinished {
    return _finished;
}

- (BOOL)isExecuting {
    return _executing;
}

- (BOOL)isAsynchronous {
    return YES;
}

@end

@implementation NSOperationQueue (AsyncBlockOperation)

- (void)addAsyncOperationWithBlock:(AsyncBlock)block {
    [self addOperation:[AsyncBlockOperation asyncBlockOperationWithBlock:block]];
}

@end

答案 1 :(得分:7)

我所做的是分别在之前和之后使用[myQueue setSuspended:YES][myQueue setSuspended:NO]

例如:

[myQueue addOperationWithBlock:^{
    [myQueue setSuspended:YES];
    [someBackendService doSomeAsyncJobWithCompletionBlock:^{
        callback(YES, nil);
        [myQueue setSuspended:NO];
    });
}];

实现的效果是队列在异步任务之前暂停,因此即使返回操作块,它也只会在异步任务时启动下一个操作(当然需要maxConcurrentOperationCount)。调用完成块。

答案 2 :(得分:1)

基于使用 setSuspended:solution of @mllm,我终于能够按顺序运行异步 HTTP GET 请求的 FOR 循环。 服务器不允许并发连接并在请求泛滥时返回错误。

以下解决方案解决了这个问题,因为下一个 NSOperation 仅在上一个操作完成后才开始:

@property (nonatomic) NSOperationQueue *myQueue;

- (void)requestVersions {
    // Create NSOperationQueue for serial retrieval:
    _myQueue = [[NSOperationQueue alloc] init];
    _myQueue.maxConcurrentOperationCount = 1;
    
    // Parse array:
    for (NSObject *object in _array) {
        // Add block operation:
        [_myQueue addOperationWithBlock:^{
            // Suspend next execution until request is completed:
            [_myQueue setSuspended:YES];
            
            // Request version async:
            [self getDetailsOfObjectWithId:object.identifier
                         completionHandler:^(NSString * _Nullable version) {
                NSLog(@"Version = %@", version);
                
                NSLog(@"_myQueue.operationCount = %lu", (unsigned long) _myQueue.operationCount);
                
                // When operations are pending, start the next:
                if (_myQueue.operationCount > 0) {
                    [_myQueue setSuspended:NO];
                }
                else {
                    // Queue is complete
                    NSLog(@"All %lu versions have been requested.", (unsigned long)[_array count]);
                }
            }];
        }];
    }
}

请注意,完成处理程序中使用了 operationCount 来了解所有操作何时完成(而不是 Key Value Observer)。

控制台日志显示请求数组中 12 个对象的版本。队列一一执行,就是想要的结果:

2021-04-10 15:02:01.911996+0200  getDetailsOfObjectWithId:completionHandler:
2021-04-10 15:02:02.001955+0200  Version = 4.1.10
2021-04-10 15:02:02.002091+0200  _myQueue.operationCount = 11
2021-04-10 15:02:02.002292+0200  getDetailsOfObjectWithId:completionHandler:
2021-04-10 15:02:02.108418+0200  Version = 1.0.18
2021-04-10 15:02:02.108611+0200  _myQueue.operationCount = 10
2021-04-10 15:02:02.108844+0200  getDetailsOfObjectWithId:completionHandler:
2021-04-10 15:02:02.201625+0200  Version = 0.0.85
2021-04-10 15:02:02.201810+0200  _myQueue.operationCount = 9
2021-04-10 15:02:02.202048+0200  getDetailsOfObjectWithId:completionHandler:
2021-04-10 15:02:02.289626+0200  Version = 3.1.0
2021-04-10 15:02:02.289851+0200  _myQueue.operationCount = 8
2021-04-10 15:02:02.290140+0200  getDetailsOfObjectWithId:completionHandler:
2021-04-10 15:02:02.369086+0200  Version = 2.0.2
2021-04-10 15:02:02.369295+0200  _myQueue.operationCount = 7
2021-04-10 15:02:02.369525+0200  getDetailsOfObjectWithId:completionHandler:
2021-04-10 15:02:02.444134+0200  Version = 1.0.11
2021-04-10 15:02:02.444270+0200  _myQueue.operationCount = 6
2021-04-10 15:02:02.444386+0200  getDetailsOfObjectWithId:completionHandler:
2021-04-10 15:02:02.513550+0200  Version = 4.0.0
2021-04-10 15:02:02.513741+0200  _myQueue.operationCount = 5
2021-04-10 15:02:02.513952+0200  getDetailsOfObjectWithId:completionHandler:
2021-04-10 15:02:02.600841+0200  Version = 1.2.4
2021-04-10 15:02:02.601030+0200  _myQueue.operationCount = 4
2021-04-10 15:02:02.601243+0200  getDetailsOfObjectWithId:completionHandler:
2021-04-10 15:02:02.691918+0200  Version = 7.0.2
2021-04-10 15:02:02.692064+0200  _myQueue.operationCount = 3
2021-04-10 15:02:02.692242+0200  getDetailsOfObjectWithId:completionHandler:
2021-04-10 15:02:02.777012+0200  Version = 3.1.81
2021-04-10 15:02:02.777116+0200  _myQueue.operationCount = 2
2021-04-10 15:02:02.777244+0200  getDetailsOfObjectWithId:completionHandler:
2021-04-10 15:02:02.864673+0200  Version = 1.0.12
2021-04-10 15:02:02.864851+0200  _myQueue.operationCount = 1
2021-04-10 15:02:02.865050+0200  getDetailsOfObjectWithId:completionHandler:
2021-04-10 15:02:02.961894+0200  Version = 1.0.7
2021-04-10 15:02:02.962073+0200  _myQueue.operationCount = 0
2021-04-10 15:02:02.962226+0200  All 12 versions have been requested.