带有嵌套完成块的 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 来传递完成处理程序的参数,但是我现在没有找到更好的解决方案。

【问题讨论】:

嗨。为什么不使用NSOperationcompletionBlock 属性?操作完成后会自动调用。在向queue添加操作之前,只需设置completionBlock即可。 NSBlockOperation的完成块是通知一个操作完成。但是如果要执行的代码有自己的完成块,它会立即被调用,因为你的代码是异步的,对吧? 我了解您的问题。我通过使用dispatch_semaphore_t 解决了这个问题。请参阅此链接,例如 https://gist.github.com/Mozilla9/16b0b5013256ff89e52b @Mozilla 是的,请发布答案,以便我接受!我使用dispatch_group_notifydispatch_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的主要内容,如果未能解决你的问题,请参考以下文章

NSBlockOperation 的粒度状态

数据库系统原理作业七数据查询中的嵌套查询

带有嵌套属性的 Rails Jquery-ui 自动完成

函数的定义和嵌套

从 NSBlockOperation 获得价值?

将嵌套数组转换为嵌套 html 块的递归 php 函数