异步块的推荐设计模式是什么?

6
我正在开发一个具有高度异步设计的iOS应用程序。在某些情况下,单个“操作”可能会排队执行许多子块,这些子块将异步执行并接收其响应(对远程服务器的调用)。任何一个子块都可能以错误状态完成执行。如果任何一个子块出现错误,任何其他子块都应该被取消,错误状态应该向上传递到父级,并执行父级的错误处理块。
我想知道在这种环境下可能推荐的设计模式和其他技巧是什么?
我知道GCD的dispatch_group_async和dispatch_group_wait功能。也许这个应用程序的设计存在缺陷,但是我在使用dispatch_group_async时没有取得良好的效果,因为组似乎不会“粘性”到子块上。
谢谢!
3个回答

5
有一段视频(2012年的WWDC),可能会对你有所帮助。它使用了自定义的NSOperationQueue,并将异步块放在NSOperations中,因此您可以掌握这些块并取消其余排队的块。
一个想法是让子块的错误处理调用处理NSOperationQueue的类中的主线程上的方法。然后,该类可以适当地取消其余部分。这样,子块只需要知道自己的线程和主线程。这是视频链接: https://developer.apple.com/videos/wwdc/2012/ 视频名为“在iOS上构建并发用户界面”。相关部分主要在后半部分,但您可能需要观看整个视频,因为它可以很好地将其放入上下文中。
编辑:
如果可能的话,我建议在嵌入的块中处理响应,这样可以将其包装得更好,这也是我认为你想要的...
//Define an NSBlockOperation, and get weak reference to it
NSBlockOperation *blockOp = [[NSBlockOperation alloc]init];
__weak NSBlockOperation *weakBlockOp = blockOp;

//Define the block and add to the NSOperationQueue, when the view controller is popped
//we can call -[NSOperationQueue cancelAllOperations] which will cancel all pending threaded ops
[blockOp addExecutionBlock: ^{

    //Once a block is executing, will need to put manual checks to see if cancel flag has been set otherwise
    //the operation will not be cancelled. The check is rather pointless in this example, but if the
    //block contained multiple lines of long running code it would make sense to do this at safe points
    if (![weakBlockOp isCancelled]) {

        //substitute code in here, possibly use *synchronous* NSURLConnection to get
        //what you need. This code will block the thread until the server response
        //completes. Hence not executing the following block and keeping it on the 
        //queue.  
        __block NSData *temp;
        response = [NSData dataWithContentsOfURL:[NSURL URLWithString:urlString]];

        [operationQueue addOperationWithBlock:^{
            if (error) {
                  dispatch_async(dispatch_get_main_queue(), ^{
                        //Call selector on main thread to handle canceling
                        //Main thread can then use handle on NSOperationQueue
                        //to cancel the rest of the blocks 
                  });
            else {
                 //Continue executing relevant code....      
            }
        }];
    }
}];
[operationQueue addOperation:blockOp];

2
谢谢,我看了视频。我想我遇到的问题是如何在异步响应等待时将操作保留在队列中?能够利用NSOperationQueue会很方便。我以前在其他应用程序中使用过该类,但以前只让队列处理出站请求-而不是响应处理。在这个应用程序中,操作直到响应被处理并且任何相关的子请求也完成后才完成。 - xyzzycoder
你能把响应处理代码放在嵌入式块中吗?我会更新我的答案。 - Sam Clewlow
如果你在NSOperation的世界里,为什么不使用[[NSOperationQueue mainQueue] addOperationWithBlock:^{}];而不是dispatch_async(dispatch_get_main_queue(),^{});?你现在的代码没问题,但是混合使用GCD调用和NSOperationQueue调用感觉有些奇怪。 - Rob
另外,为什么在获取dataWithContentsOfURL后,您要将另一个块添加到此块正在运行的相同队列中,当该代码只会将某些内容分派到主队列时。为什么不立即分派到主队列。您的后台队列可能会被阻塞,但是您的主队列却没有。不确定为什么您要推迟处理错误的操作。 - Rob
是的,我认为你说得对,可以使用块来完成,但这会使它更加复杂,因为这里唯一在主线程上调用的是处理来自 NSURLConnection 的错误。如果没有错误,则块内的代码将继续执行。 - Sam Clewlow
我最近发现了PromiseKit(https://github.com/mxcl/PromiseKit)。这个工具包所倡导的模式值得探索。 - xyzzycoder

1

-1

在Cocoa中,有许多实现异步行为的方法。

GCD、NSOperationQueue、performSelectorAfterDelay以及创建自己的线程等机制都有适当的使用时机。这里不便详细讨论,但你在帖子中提到的某些问题需要解决。

如果任何子块发生错误,其他子块应该被取消,错误状态应该向上传递到父级,并执行父级的错误处理块。

块无法将错误传递到堆栈上。没有例外。


谢谢。在这个应用程序中,我与服务器有一个通信渠道,并且客户端/服务器操作是隐式串行的。我不试图将任何错误传递到堆栈上方。 - xyzzycoder
那么你所需要的就是一个异步调用 - 使用GCD,或者创建自己的线程。 - deleted_user

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接