带有嵌套完成块的 NSBlockOperation
Posted
技术标签:
【中文标题】带有嵌套完成块的 NSBlockOperation【英文标题】:NSBlockOperation with Nested Completion Block 【发布时间】:2015-10-21 14:39:44 【问题描述】:应该有一个带有完成块的方法来执行:
[container insert:data
completion:^(NSDictionary *result, NSError *error)
];
我需要使用NSOperation
使这个并发(超过GCD
调度块,因为我需要更多地控制操作流程和取消)。
现在,假设执行一个正常的完成块,我可以使用 NSBlockOperation
之类的
- (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;
所以这样称呼它
[self executeBlock:^
/// my sync code
inQueue:operationQueue
completion:^(BOOL finished)
];
问题在于那里有异步代码:
void (^completionBlock)() = ^void()
// this is the NSOperation completion block where sync code is executed
;
和
void (^insertCompletionBlock)(NSDictionary *, NSError *) = ^void(NSDictionary *result, NSError *error)
// this is the insert api completion block
;
所以有
[container insert:data completion:insertCompletionBlock];
如果我像这样进行嵌套调用
[self executeBlock:^
[container insert:data
completion:^(NSDictionary *result, NSError *error)
];
inQueue:operationQueue
completion:^(BOOL finished)
];
此NSOperation
将立即结束,因为insert:completion:
方法将在其调用完成后返回。
那么,如何序列化这个执行,以便在insert:completion:
的嵌套完成块被执行后调用NSBlockOperation
?
[更新]
使用@Mozilla 的解决方案
我提出了一个自定义的NSBlockOperation
,我用它来添加一些属性:
@interface MyCloudOperation: NSBlockOperation
@property(nonatomic,strong) id result;
@property(nonatomic,strong) NSError *error;
@end
@implementation MXMCloudOperation
@end
还有这个
MyCloudOperation *blockOp=[[MyCloudOperation alloc] init];
__weak MXMCloudOperation *weakBlockOp=blockOp;
[blockOp setCompletionBlock:^
if(completion) completion(weakBlockOp.result,weakBlockOp.error);
];
[blockOp addExecutionBlock:^
dispatch_semaphore_t mutex = dispatch_semaphore_create(0);
void (^insertCompletionBlock)(NSDictionary *, NSError *) = ^void(NSDictionary *result, NSError *error)
if(error)
weakBlockOp.error=error;
NSLog(@"Error saving to %@ data\n%@", containerName,
error.localizedDescription);
else
weakBlockOp.result=result;
NSLog(@"Data %@ sent", result);
dispatch_semaphore_signal(mutex);
;
[container insert:data completion:insertCompletionBlock];
dispatch_semaphore_wait(mutex, DISPATCH_TIME_FOREVER);
];
[operationQueue addOperation:blockOp];
这里我不喜欢的是引用我的NSBlockOperation
来传递完成处理程序的参数,但是我现在没有找到更好的解决方案。
【问题讨论】:
嗨。为什么不使用NSOperation
的completionBlock
属性?操作完成后会自动调用。在向queue
添加操作之前,只需设置completionBlock
即可。
NSBlockOperation
的完成块是通知一个操作完成。但是如果要执行的代码有自己的完成块,它会立即被调用,因为你的代码是异步的,对吧?
我了解您的问题。我通过使用dispatch_semaphore_t
解决了这个问题。请参阅此链接,例如 https://gist.github.com/Mozilla9/16b0b5013256ff89e52b
@Mozilla 是的,请发布答案,以便我接受!我使用dispatch_group_notify
和dispatch_group_async
尝试了相同的解决方案,但唯一的方法是dispatch_semaphore_t
+1。谢谢。
【参考方案1】:
我通过使用dispatch_semaphore_t
解决了这个问题。
- (void)saveWebDataInternal:(ResponseModel *)data completion:(void(^)(NSArray *))completion
NSBlockOperation *op = [[NSBlockOperation alloc] init];
op.completionBlock = ^
dispatch_async(dispatch_get_main_queue(), ^
[self loadCachedDataInternal:completion];
);
;
[op addExecutionBlock:^
dispatch_semaphore_t mutex = dispatch_semaphore_create(0);
[self.cacheDAO asyncImport:data completion:^
dispatch_semaphore_signal(mutex);
];
dispatch_semaphore_wait(mutex, DISPATCH_TIME_FOREVER);
];
// start operation
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^
[op start];
);
【讨论】:
这可以进一步改进,假设我想将参数从asyncImport
完成传递到saveWebDataInternal
完成处理程序,假设一个错误和类似(void(^)(id result, NSError *error))completion
的结果。一个解决方案可能是使用自定义的NSBlockOperation
来支持,就像我的示例中的insert
完成参数一样。就像在 Java 中将参数传递给构造函数中的 Runnable
线程一样。有更好的主意吗?
@loretoparisi 添加了要点https://gist.github.com/Mozilla9/1530db57f21f860821a2。看起来像您的决定,但使用了__block
变量以上是关于带有嵌套完成块的 NSBlockOperation的主要内容,如果未能解决你的问题,请参考以下文章