我需要将异步操作放入操作队列,但是,它们需要在另一个
之后执行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) {
}];
}];
答案 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
需要在您的子类中定义,并且您需要覆盖isExecuting
和isFinished
属性才能返回正确的值。
如果你将所有这些放在一起,以及一个带有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.