如何使用Objective-C中的块进行结构化编程

时间:2022-09-07 11:12:56

When using methods which return blocks they can be very convenient. However, when you have to string a few of them together it gets messy really quickly

当使用返回块的方法时,它们可以非常方便。但是,当你必须将它们中的一些串在一起时,它会很快变得混乱

for instance, you have to call 4 URLs in succession:

例如,您必须连续调用4个URL:

[remoteAPIWithURL:url1 success:^(int status){
    [remoteAPIWithURL:url2 success:^(int status){
        [remoteAPIWithURL:url3 success:^(int status){
            [remoteAPIWithURL:url2 success:^(int status){
            //succes!!!
            }];
        }];
    }];
}];

So for every iteration I go one level deeper, and I don't even handle errors in the nested blocks yet.

因此,对于每次迭代,我都会更深入一级,我甚至都没有处理嵌套块中的错误。

It gets worse when there is an actual loop. For instance, say I want to upload a file in 100 chunks:

当存在实际循环时,它会变得更糟。例如,假设我想以100个块的形式上传文件:

- (void) continueUploadWithBlockNr:(int)blockNr
{
    if(blocknr>=100) 
    {
    //success!!!
    }
    [remoteAPIUploadFile:file withBlockNr:blockNr success:^(int status)
    {
        [self continueUploadWithBlockNr:blockNr];
    }];
}

This feels very unintuitive, and gets very unreadable very quick.

这感觉非常不直观,并且非常快速地变得非常难以理解。

In .Net they solved all this using the async and await keyword, basically unrolling these continuations into a seemingly synchronous flow.

在.Net中,他们使用async和await关键字解决了所有这些问题,基本上将这些延续展开为一个看似同步的流程。

What is the best practice in Objective C?

Objective C中的最佳实践是什么?

8 个解决方案

#1


4  

Your question immediately made me think of recursion. Turns out, Objective-c blocks can be used in recursion. So I came up with the following solution, which is easy to understand and can scale to N tasks pretty nicely.

你的问题立刻让我想起了递归。事实证明,Objective-c块可用于递归。所以我提出了以下解决方案,这个解决方案很容易理解,可以很好地扩展到N个任务。

// __block declaration of the block makes it possible to call the block from within itself
__block void (^urlFetchBlock)();

// Neatly aggregate all the urls you wish to fetch
NSArray *urlArray = @[
    [NSURL URLWithString:@"http://www.google.com"],
    [NSURL URLWithString:@"http://www.*.com"],
    [NSURL URLWithString:@"http://www.bing.com"],
    [NSURL URLWithString:@"http://www.apple.com"]
];
__block int urlIndex = 0;

// the 'recursive' block 
urlFetchBlock = [^void () {
    if (urlIndex < (int)[urlArray count]){
        [self remoteAPIWithURL:[urlArray objectAtIndex:index] 
            success:^(int theStatus){
                urlIndex++;
                urlFetchBlock();
            }

            failure:^(){
                // handle error. 
            }];
    }
} copy];

// initiate the url requests
urlFetchBlock();

#2


2  

One way to reduce nesting is to define methods that return the individual blocks. In order to facilitate the data sharing which is done "auto-magically" by the Objective C compiler through closures, you would need to define a separate class to hold the shared state.

减少嵌套的一种方法是定义返回单个块的方法。为了便于Objective C编译器通过闭包“自动神奇地”完成数据共享,您需要定义一个单独的类来保存共享状态。

Here is a rough sketch of how this can be done:

这是一个如何做到这一点的草图:

typedef void (^WithStatus)(int);

@interface AsyncHandler : NSObject {
    NSString *_sharedString;
    NSURL *_innerUrl;
    NSURL *_middleUrl;
    WithStatus _innermostBlock;
}
+(void)handleRequest:(WithStatus)innermostBlock
            outerUrl:(NSURL*)outerUrl
            middleUrl:(NSURL*)middleUrl
            innerUrl:(NSURL*)innerUrl;

-(WithStatus)outerBlock;

-(WithStatus)middleBlock;

@end

@implementation AsyncHandler

+(void)handleRequest:(WithStatus)innermostBlock
            outerUrl:(NSURL*)outerUrl
            middleUrl:(NSURL*)middleUrl
            innerUrl:(NSURL*)innerUrl {
    AsyncHandler *h = [[AsyncHandler alloc] init];
    h->_innermostBlock = innermostBlock;
    h->_innerUrl = innerUrl;
    h->_middleUrl = middleUrl;
    [remoteAPIWithURL:outerUrl success:[self outerBlock]];
}

-(WithStatus)outerBlock {
    return ^(int success) {
        _sharedString = [NSString stringWithFormat:@"Outer: %i", success];
        [remoteAPIWithURL:_middleUrl success:[self middleBlock]];
    };
}

-(WithStatus)middleBlock {
    return ^(int success) {
        NSLog("Shared string: %@", _sharedString);
        [remoteAPIWithURL:_innerUrl success:_innermostBlock];
    };
}

@end

Note: All of this assumes ARC; if you are compiling without it, you need to use Block_copy in the methods returning blocks. You would also need to do a copy in the calling code below.

注意:所有这些都假设为ARC;如果您在没有它的情况下进行编译,则需要在返回块的方法中使用Block_copy。您还需要在下面的调用代码中进行复制。

Now your original function can be re-written without the "Russian doll" nesting, like this:

现在您的原始功能可以在没有“俄罗斯娃娃”嵌套的情况下重写,如下所示:

[AsyncHandler
    handleRequest:^(int status){
        //succes!!!
    }
    outerUrl:[NSURL @"http://my.first.url.com"]
    middleUrl:[NSURL @"http://my.second.url.com"]
    innerUrl:[NSURL @"http://my.third.url.com"]
];

#3


2  

Iterative algorithm:

  • Create a __block variable (int urlNum) to keep track of the current URL (inside an NSArray of them).
  • 创建一个__block变量(int urlNum)来跟踪当前URL(在它们的NSArray中)。

  • Have the onUrlComplete block fire off the next request until all URLs have been loaded.
  • 让onUrlComplete块触发下一个请求,直到所有URL都被加载。

  • Fire the first request.
  • 解雇第一个请求。

  • When all URLs have been loaded, do the "//success!" dance.
  • 加载完所有网址后,请执行“// success!”舞蹈。

Code written without the aid of XCode (meaning, there may be compiler errors -- will fix if necessary):

在没有XCode的帮助下编写的代码(意思是,可能存在编译器错误 - 必要时将修复):

- (void)loadUrlsAsynchronouslyIterative:(NSArray *)urls {
  __block int urlNum = 0;
  void(^onUrlComplete)(int) = nil; //I don't remember if you can call a block from inside itself.
  onUrlComplete = ^(int status) {
    if (urlNum < urls.count) {
      id nextUrl = urls[urlNum++];
      [remoteAPIWithURL:nextUrl success:onUrlComplete];
    } else {
      //success!
    }
  }
  onUrlComplete(0); //fire first request
}

Recursive algorithm:

  • Create a method to load all the remaining URLs.
  • 创建一个方法来加载所有剩余的URL。

  • When remaining URLs is empty, fire "onSuccess".
  • 当剩余的URL为空时,触发“onSuccess”。

  • Otherwise, fire request for the next URL and provide a completion block that recursively calls the method with all but the first remaining URLs.
  • 否则,触发下一个URL的请求并提供一个完成块,该块以递归方式调用除了第一个剩余URL之外的所有URL。

  • Complications: we declared the "onSuccess" block to accept an int status parameter, so we pass the last status variable down (including a "default" value).
  • 并发症:我们声明“onSuccess”块接受一个int状态参数,因此我们将最后一个状态变量向下传递(包括“默认”值)。

Code written without the aid of XCode (bug disclaimer here):

没有XCode帮助编写的代码(这里的bug免责声明):

- (void)loadUrlsAsynchronouslyRecursive:(NSArray *)remainingUrls onSuccess:(void(^)(int status))onSuccess lastStatus:(int)lastStatus {
  if (remainingUrls.count == 0) {
    onSuccess(lastStatus);
    return;
  }
  id nextUrl = remainingUrls[0];
  remainingUrls = [remainingUrls subarrayWithRange:NSMakeRange(1, remainingUrls.count-1)];
  [remoteAPIWithUrl:nextUrl onSuccess:^(int status) {
    [self loadUrlsAsynchronouslyRecursive:remainingUrls onSuccess:onSuccess lastStatus:status];
  }];
}

//fire first request:
[self loadUrlsAsynchronouslyRecursive:urls onSuccess:^(int status) {
  //success here!
} lastStatus:0];

Which is better?

哪个更好?

  • The iterative algorithm is simple and concise -- if you're comfortable playing games with __block variables and scopes.
  • 迭代算法简单明了 - 如果你习惯用__block变量和范围玩游戏。

  • Alternatively, the recursive algorithm doesn't require __block variables and is fairly simple, as recursive algorithms go.
  • 或者,递归算法不需要__block变量,并且非常简单,就像递归算法一样。

  • The recursive implementation is more re-usable that the iterative one (as implemented).
  • 递归实现比迭代实现更具可重用性(如实现的那样)。

  • The recursive algorithm might leak (it requires a reference to self), but there are several ways to fix that: make it a function, use __weak id weakSelf = self;, etc.
  • 递归算法可能会泄漏(它需要引用self),但有几种方法可以解决这个问题:使它成为一个函数,使用__weak id weakSelf = self;等。

How easy would it be to add error-handling?

添加错误处理有多容易?

  • The iterative implementation can easily be extended to check the value of status, at the cost of the onUrlComplete block becoming more complex.
  • 迭代实现可以很容易地扩展以检查状态的值,代价是onUrlComplete块变得更加复杂。

  • The recursive implementation is perhaps not as straight-forward to extend -- primarily because it is re-usable. Do you want to cancel loading more URLs when the status is such-and-such? Then pass down a status-checking/error-handling block that accepts int status and returns BOOL (for example YES to continue, NO to cancel). Or perhaps modify onSuccess to accept both int status and NSArray *remainingUrls -- but you'll need to call loadUrlsAsynchronouslyRecursive... in your onSuccess block implementation.
  • 递归实现可能不是直接扩展 - 主要是因为它是可重用的。当状态如此时,是否要取消加载更多URL?然后传递接受int状态并返回BOOL的状态检查/错误处理块(例如,YES继续,NO取消)。或者修改onSuccess以接受int status和NSArray * remainingUrls - 但是你需要在onSuccess块实现中调用loadUrlsAsynchronouslyRecursive ....

#4


1  

You said (in a comment), “asynchronous methods offer easy asynchronisity without using explicit threads.” But your complaint seems to be that you're trying to do something with asynchronous methods, and it's not easy. Do you see the contradiction here?

你说(在评论中),“异步方法提供了简单的异步性,而不使用显式线程。”但你的抱怨似乎是你试图用异步方法做某事,这并不容易。你看到这里的矛盾吗?

When you use a callback-based design, you sacrifice the ability to express your control flow directly using the language's built-in structures.

当您使用基于回调的设计时,您会牺牲使用语言的内置结构直接表达控制流的能力。

So I suggest you stop using a callback-based design. Grand Central Dispatch (GCD) makes it easy (that word again!) to perform work “in the background”, and then call back to the main thread to update the user interface. So if you have a synchronous version of your API, just use it in a background queue:

所以我建议你停止使用基于回调的设计。 Grand Central Dispatch(GCD)可以轻松(再次说明这个词!)在“后台”执行工作,然后回调主线程来更新用户界面。因此,如果您有API的同步版本,只需在后台队列中使用它:

- (void)interactWithRemoteAPI:(id<RemoteAPI>)remoteAPI {
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        // This block runs on a background queue, so it doesn't block the main thread.
        // But it can't touch the user interface.

        for (NSURL *url in @[url1, url2, url3, url4]) {
            int status = [remoteAPI syncRequestWithURL:url];
            if (status != 0) {
                dispatch_async(dispatch_get_main_queue(), ^{
                    // This block runs on the main thread, so it can update the
                    // user interface.
                    [self remoteRequestFailedWithURL:url status:status];
                });
                return;
            }
        }
    });
}

Since we're just using normal control flow, it's straightforward to do more complicated things. Say we need to issue two requests, then upload a file in chunks of at most 100k, then issue one more request:

由于我们只是使用正常的控制流程,因此可以直接执行更复杂的操作。假设我们需要发出两个请求,然后以最多100k的块为单位上传文件,然后再发出一个请求:

#define AsyncToMain(Block) dispatch_async(dispatch_get_main_queue(), Block)

- (void)uploadFile:(NSFileHandle *)fileHandle withRemoteAPI:(id<RemoteAPI>)remoteAPI {
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        int status = [remoteAPI syncRequestWithURL:url1];
        if (status != 0) {
            AsyncToMain(^{ [self remoteRequestFailedWithURL:url1 status:status]; });
            return;
        }

        status = [remoteAPI syncRequestWithURL:url2];
        if (status != 0) {
            AsyncToMain(^{ [self remoteRequestFailedWithURL:url2 status:status]; });
            return;
        }

        while (1) {
            // Manage an autorelease pool to avoid accumulating all of the
            // 100k chunks in memory simultaneously.
            @autoreleasepool {
                NSData *chunk = [fileHandle readDataOfLength:100 * 1024];
                if (chunk.length == 0)
                    break;
                status = [remoteAPI syncUploadChunk:chunk];
                if (status != 0) {
                    AsyncToMain(^{ [self sendChunkFailedWithStatus:status]; });
                    return;
                }
            }
        }

        status = [remoteAPI syncRequestWithURL:url4];
        if (status != 0) {
            AsyncToMain(^{ [self remoteRequestFailedWithURL:url4 status:status]; });
            return;
        }

        AsyncToMain(^{ [self uploadFileSucceeded]; });
    });
}

Now I'm sure you're saying “Oh yeah, that looks great.” ;^) But you might also be saying “What if RemoteAPI only has asynchronous methods, not synchronous methods?”

现在我确定你会说“哦,是的,看起来很棒。”; ^)但你也可能会说“如果RemoteAPI只有异步方法,而不是同步方法怎么办?”

We can use GCD to create a synchronous wrapper for an asynchronous method. We need to make the wrapper call the async method, then block until the async method calls the callback. The tricky bit is that perhaps we don't know which queue the async method uses to invoke the callback, and we don't know if it uses dispatch_sync to call the callback. So let's be safe by calling the async method from a concurrent queue.

我们可以使用GCD为异步方法创建同步包装器。我们需要让包装器调用异步方法,然后阻塞,直到异步方法调用回调。棘手的一点是,我们可能不知道异步方法使用哪个队列来调用回调,我们不知道它是否使用dispatch_sync来调用回调。因此,通过从并发队列调用异步方法来确保安全。

- (int)syncRequestWithRemoteAPI:(id<RemoteAPI>)remoteAPI url:(NSURL *)url {
    __block int outerStatus;
    dispatch_semaphore_t sem = dispatch_semaphore_create(0);
    [remoteAPI asyncRequestWithURL:url completion:^(int status) {
        outerStatus = status;
        dispatch_semaphore_signal(sem);
    }];
    dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
    dispatch_release(sem);
    return outerStatus;
}

UPDATE

I will respond to your third comment first, and your second comment second.

我将首先回复您的第三条评论,然后再回复您的第二条评论。

Third Comment

Your third comment:

你的第三个评论:

Last but not least, your solution of dedicating a separate thread to wrap around the synchronous version of a call is more costly than using the async alternatives. a Thread is an expensive resource, and when it is blocking you basically have lost one thread. Async calls (the ones in the OS libraries at least) are typically handled in a much more efficient way. (For instance, if you would request 10 urls at the same time, chances are it will not spin up 10 threads (or put them in a threadpool))

最后但同样重要的是,您使用单独的线程来绕过调用的同步版本的解决方案比使用异步备选方案更昂贵。一个线程是一个昂贵的资源,当它阻塞你基本上丢失了一个线程。异步调用(至少是OS库中的调用)通常以更有效的方式处理。 (例如,如果您同时请求10个网址,则可能不会启动10个线程(或将它们放入线程池中))

Yes, using a thread is more expensive than just using the asynchronous call. So what? The question is whether it's too expensive. Objective-C messages are too expensive in some scenarios on current iOS hardware (the inner loops of a real-time face detection or speech recognition algorithm, for example), but I have no qualms about using them most of the time.

是的,使用线程比使用异步调用更昂贵。所以呢?问题是它是否过于昂贵。 Objective-C消息在当前iOS硬件(例如,实时面部检测或语音识别算法的内部循环)的某些情况下过于昂贵,但我对大多数时间使用它们没有任何疑虑。

Whether a thread is “an expensive resource” really depends on the context. Let's consider your example: “For instance, if you would request 10 urls at the same time, chances are it will not spin up 10 threads (or put them in a threadpool)”. Let's find out.

线程是否是“昂贵的资源”实际上取决于上下文。让我们考虑你的例子:“例如,如果你同时请求10个网址,很可能不会启动10个线程(或将它们放在线程池中)”。让我们来看看。

NSURL *url = [NSURL URLWithString:@"http://1.1.1.1/"];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
for (int i = 0; i < 10; ++i) {
    [NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
        NSLog(@"response=%@ error=%@", response, error);
    }];
}

So here I am using Apple's own recommended +[NSURLConnection sendAsynchronousRequest:queue:completionHandler:] method to send 10 requests asynchronously. I've chosen the URL to be non-responsive, so I can see exactly what kind of thread/queue strategy Apple uses to implement this method. I ran the app on my iPhone 4S running iOS 6.0.1, paused in the debugger, and took a screen shot of the Thread Navigator:

所以我在这里使用Apple自己推荐的+ [NSURLConnection sendAsynchronousRequest:queue:completionHandler:]方法异步发送10个请求。我选择了URL无响应,因此我可以确切地看到Apple使用什么样的线程/队列策略来实现此方法。我在运行iOS 6.0.1的iPhone 4S上运行应用程序,在调试器中暂停,并拍摄了Thread Navigator的屏幕截图:

如何使用Objective-C中的块进行结构化编程

You can see that there are 10 threads labeled com.apple.root.default-priority. I've opened three of them so you can see that they are just normal GCD queue threads. Each calls a block defined in +[NSURLConnection sendAsynchronousRequest:…], which just turns around and calls +[NSURLConnection sendSynchronousRequest:…]. I checked all 10, and they all have the same stack trace. So, in fact, the OS library does spin up 10 threads.

您可以看到有10个线程标记为com.apple.root.default-priority。我打开了其中三个,这样你就可以看到它们只是普通的GCD队列线程。每个调用一个在+ [NSURLConnection sendAsynchronousRequest:...]中定义的块,它只是转身并调用+ [NSURLConnection sendSynchronousRequest:...]。我检查了所有10个,它们都有相同的堆栈跟踪。因此,事实上,OS库确实启动了10个线程。

I bumped the loop count from 10 to 100 and found that GCD caps the number of com.apple.root.default-priority threads at 64. So my guess is the other 36 requests I issued are queued up in the global default-priority queue, and won't even start executing until some of the 64 “running” requests finish.

我将循环计数从10增加到100,并发现GCD在64处限制了com.apple.root.default优先级线程的数量。所以我的猜测是我发出的其他36个请求在全局默认优先级队列中排队,甚至在64个“运行”请求中的某些请求完成后才会开始执行。

So, is it too expensive to use a thread to turn an asynchronous function into a synchronous function? I'd say it depends on how many of these you plan to do simultaneously. I would have no qualms if the number's under 10, or even 20.

那么,使用线程将异步函数转换为同步函数是否过于昂贵?我会说这取决于你打算同时做多少这些。如果数字低于10,甚至20,我都不会有任何疑虑。

Second Comment

Which brings me to your second comment:

这让我想到你的第二个评论:

However, when you have: do these 3 things at the same time, and when 'any' of them is finished then ignore the rest and do these 3 calls at the same time and when 'all' of them finish then succes.

然而,当你有:同时做这三件事,当'任何'完成后,然后忽略其余的,同时做这3个电话,当'all'完成然后成功。

These are cases where it's easy to use GCD, but we can certainly combine the GCD and async approaches to use fewer threads if you want, while still using the languages native tools for control flow.

这些是易于使用GCD的情况,但我们当然可以将GCD和异步方法结合使用,以便在需要时使用更少的线程,同时仍然使用语言本机工具来控制流。

First, we'll make a typedef for the remote API completion block, just to save typing later:

首先,我们将为远程API完成块创建一个typedef,以便稍后保存输入:

typedef void (^RemoteAPICompletionBlock)(int status);

I'll start the control flow the same way as before, by moving it off the main thread to a concurrent queue:

我将以与以前相同的方式启动控制流,将其从主线程移动到并发队列:

- (void)complexFlowWithRemoteAPI:(id<RemoteAPI>)remoteAPI {
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

First we want to issue three requests simultaneously and wait for one of them to succeed (or, presumably, for all three to fail).

首先,我们要同时发出三个请求并等待其中一个请求成功(或者,假设所有三个请求都失败)。

So let's say we have a function, statusOfFirstRequestToSucceed, that issues any number of asynchronous remote API requests and waits for the first to succeed. This function will provide the completion block for each async request. But the different requests might take different arguments… how can we pass the API requests to the function?

因此,假设我们有一个函数statusOfFirstRequestToSucceed,它发出任意数量的异步远程API请求并等待第一个成功。此函数将为每个异步请求提供完成块。但是不同的请求可能会采用不同的论点......我们如何将API请求传递给函数?

We can do it by passing a literal block for each API request. Each literal block takes the completion block and issues the asynchronous remote API request:

我们可以通过为每个API请求传递一个文字块来实现。每个文字块都使用完成块并发出异步远程API请求:

        int status = statusOfFirstRequestToSucceed(@[
            ^(RemoteAPICompletionBlock completion) {
                [remoteAPI requestWithCompletion:completion];
            },
            ^(RemoteAPICompletionBlock completion) {
                [remoteAPI anotherRequestWithCompletion:completion];
            },
            ^(RemoteAPICompletionBlock completion) {
                [remoteAPI thirdRequestWithCompletion:completion];
            }
        ]);
        if (status != 0) {
            AsyncToMain(^{ [self complexFlowFailedOnFirstRoundWithStatus:status]; });
            return;
        }

OK, now we've issued the three first parallel requests and waited for one to succeed, or for all of them to fail. Now we want to issue three more parallel requests and wait for all to succeed, or for one of them to fail. So it's almost identical, except I'm going to assume a function statusOfFirstRequestToFail:

好的,现在我们已经发布了三个第一个并行请求并等待一个成功,或者所有这些请求都失败了。现在我们要发出三个并行请求并等待所有成功,或者其中一个请求失败。所以它几乎完全相同,除了我将假设一个函数statusOfFirstRequestToFail:

        status = statusOfFirstRequestToFail(@[
            ^(RemoteAPICompletionBlock completion) {
                [remoteAPI requestWithCompletion:completion];
            },
            ^(RemoteAPICompletionBlock completion) {
                [remoteAPI anotherRequestWithCompletion:completion];
            },
            ^(RemoteAPICompletionBlock completion) {
                [remoteAPI thirdRequestWithCompletion:completion];
            }
        ]);
        if (status != 0) {
            AsyncToMain(^{ [self complexFlowFailedOnSecondRoundWithStatus:status]; });
            return;
        }

Now both rounds of parallel requests have finished, so we can notify the main thread of success:

现在两轮并行请求都已完成,因此我们可以通知成功的主线程:

        [self complexFlowSucceeded];
    });
}

Overall, that seems like a pretty straightforward flow of control to me, and we just need to implement statusOfFirstRequestToSucceed and statusOfFirstRequestToFail. We can implement them with no extra threads. Since they are so similar, we'll make them both call on a helper function that does the real work:

总的来说,这似乎是一个非常直接的控制流程,我们只需要实现statusOfFirstRequestToSucceed和statusOfFirstRequestToFail。我们可以实现它们而不需要额外的线程。因为它们非常相似,所以我们会让它们都调用辅助函数来完成真正的工作:

static int statusOfFirstRequestToSucceed(NSArray *requestBlocks) {
    return statusOfFirstRequestWithStatusPassingTest(requestBlocks, ^BOOL (int status) {
        return status == 0;
    });
}

static int statusOfFirstRequestToFail(NSArray *requestBlocks) {
    return statusOfFirstRequestWithStatusPassingTest(requestBlocks, ^BOOL (int status) {
        return status != 0;
    });
}

In the helper function, I'll need a queue in which to run the completion blocks, to prevent race conditions:

在辅助函数中,我需要一个队列来运行完成块,以防止竞争条件:

static int statusOfFirstRequestWithStatusPassingTest(NSArray *requestBlocks,
    BOOL (^statusTest)(int status))
{
    dispatch_queue_t completionQueue = dispatch_queue_create("remote API completion", 0);

Note that I will only put blocks on completionQueue using dispatch_sync, and dispatch_sync always runs the block on the current thread unless the queue is the main queue.

请注意,我将仅使用dispatch_sync将块放在completionQueue上,并且dispatch_sync始终在当前线程上运行块,除非队列是主队列。

I'll also need a semaphore, to wake up the outer function when some request has completed with a passing status, or when all requests have finished:

我还需要一个信号量,当某个请求以传递状态完成时唤醒外部函数,或者所有请求都已完成时:

    dispatch_semaphore_t enoughJobsCompleteSemaphore = dispatch_semaphore_create(0);

I'll keep track of the number of jobs not yet finished and the status of the last job to finish:

我将跟踪尚未完成的工作数量以及最后完成的工作的状态:

    __block int jobsLeft = requestBlocks.count;
    __block int outerStatus = 0;

When jobsLeft becomes 0, it means that either I've set outerStatus to a status that passes the test, or that all jobs have completed. Here's the completion block where I'll the work of tracking whether I'm done waiting. I do it all on completionQueue to serialize access to jobsLeft and outerStatus, in case the remote API dispatches multiple completion blocks in parallel (on separate threads or on a concurrent queue):

当jobsLeft变为0时,表示我将outerStatus设置为通过测试的状态,或者所有作业都已完成。这是完成块,我将跟踪我是否已经完成等待。我在completionQueue上全部执行以序列化对jobsLeft和outerStatus的访问,以防远程API并行调度多个完成块(在单独的线程或并发队列上):

    RemoteAPICompletionBlock completionBlock = ^(int status) {
        dispatch_sync(completionQueue, ^{

I check to see if the outer function is still waiting for the current job to complete:

我检查外部函数是否仍在等待当前作业完成:

            if (jobsLeft == 0) {
                // The outer function has already returned.
                return;
            }

Next, I decrement the number of jobs remaining and make the completed job's status available to the outer function:

接下来,我减少剩余的作业数量,并将完成的作业状态提供给外部函数:

            --jobsLeft;
            outerStatus = status;

If the completed job's status passes the test, I set jobsLeft to zero to prevent other jobs from overwriting my status or singling the outer function:

如果已完成作业的状态通过测试,我将jobsLeft设置为零以防止其他作业覆盖我的状态或单独使用外部函数:

            if (statusTest(status)) {
                // We have a winner.  Prevent other jobs from overwriting my status.
                jobsLeft = 0;
            }

If there are no jobs left to wait on (because they've all finished or because this job's status passed the test), I wake up the outer function:

如果没有剩余的工作要等待(因为他们已经完成或因为这个工作的状态通过了测试),我唤醒了外部功能:

            if (jobsLeft == 0) {
                dispatch_semaphore_signal(enoughJobsCompleteSemaphore);
            }

Finally, I release the queue and the semaphore. (The retains will be later, when I loop through the request blocks to execute them.)

最后,我释放队列和信号量。 (当我遍历请求块以执行它们时,保留将会更晚。)

            dispatch_release(completionQueue);
            dispatch_release(enoughJobsCompleteSemaphore);
        });
    };

That's the end of the completion block. The rest of the function is trivial. First I execute each request block, and I retain the queue and the semaphore to prevent dangling references:

那是完成块的结束。其余的功能是微不足道的。首先,我执行每个请求块,并保留队列和信号量以防止悬空引用:

    for (void (^requestBlock)(RemoteAPICompletionBlock) in requestBlocks) {
        dispatch_retain(completionQueue); // balanced in completionBlock
        dispatch_retain(enoughJobsCompleteSemaphore); // balanced in completionBlock
        requestBlock(completionBlock);
    }

Note that the retains aren't necessary if you're using ARC and your deployment target is iOS 6.0 or later.

请注意,如果您使用ARC且部署目标是iOS 6.0或更高版本,则不需要保留。

Then I just wait for one of the jobs to wake me up, release the queue and the semaphore, and return the status of the job that woke me:

然后我只是等待其中一个工作唤醒我,释放队列和信号量,并返回唤醒我的工作状态:

    dispatch_semaphore_wait(enoughJobsCompleteSemaphore, DISPATCH_TIME_FOREVER);
    dispatch_release(completionQueue);
    dispatch_release(enoughJobsCompleteSemaphore);
    return outerStatus;
}

Note that the structure of statusOfFirstRequestWithStatusPassingTest is fairly generic: you can pass any request blocks you want, as long as each one calls the completion block and passes in an int status. You could modify the function to handle a more complex result from each request block, or to cancel outstanding requests (if you have a cancellation API).

请注意,statusOfFirstRequestWithStatusPassingTest的结构非常通用:您可以传递所需的任何请求块,只要每个请求块都调用完成块并传入int状态。您可以修改函数以处理来自每个请求块的更复杂的结果,或者取消未完成的请求(如果您有取消API)。

#5


0  

While researching this myself I bumped into a port of Reactive Extensions to Objective-C. Reactive Extensions is like having the ability to querying a set of events or asynchronous operations. I know it has had a big uptake under .Net and JavaScript, and now apparently there is a port for Objective-C as well

在自己研究这个问题时,我碰到了一个针对Objective-C的Reactive Extensions端口。 Reactive Extensions就像具有查询一组事件或异步操作的能力。我知道它在.Net和JavaScript下已经有了很大的发展,现在显然还有一个Objective-C的端口

https://github.com/blog/1107-reactivecocoa-for-a-better-world

Syntax looks tricky. I wonder if there is real world experience with it for iPhone development and if it does actually solve this issue elegantly.

语法看起来很棘手。我想知道iPhone开发是否有真实的世界经验,如果它确实优雅地解决了这个问题。

#6


0  

I tend to wrap big nested block cluster f**** like you describe in subclasses of NSOperation that describe what the overall behaviour that your big nest block cluster f*** is actually doing (rather than leaving them littered throughout other code).

我倾向于包装大型嵌套块集群f ****,就像你在NSOperation的子类中描述的那样,它描述了你的大嵌套块f ***实际上正在做的整体行为(而不是让它们在其他代码中散落)。

For example if your following code:

例如,如果您的代码如下:

[remoteAPIWithURL:url1 success:^(int status){
    [remoteAPIWithURL:url2 success:^(int status){
        [remoteAPIWithURL:url3 success:^(int status){
            [remoteAPIWithURL:url2 success:^(int status){
            //succes!!!
            }];
        }];
    }];
}];

is intended to get an authorise token and then sync something perhaps it would be an NSAuthorizedSyncOperation… I'm sure you get the gist. Benefits of this are nice tidy bundles of behaviour wrapped up in a class with one place to edit them if things change down the line. My 2¢.

旨在获取授权令牌,然后同步一些可能是NSAuthorizedSyncOperation ...我相信你得到了要点。这样做的好处是在一个类中包含了很好的整洁行为,如果事情发生变化,就会在一个地方编辑它们。我的2¢。

#7


0  

In NSDocument the following methods are available for serialization:

在NSDocument中,以下方法可用于序列化:

Serialization
– continueActivityUsingBlock:
– continueAsynchronousWorkOnMainThreadUsingBlock:
– performActivityWithSynchronousWaiting:usingBlock:
– performAsynchronousFileAccessUsingBlock:
– performSynchronousFileAccessUsingBlock:

I'm just digging into this, but it seems like this would be a good place to start.

我只是深入研究这一点,但似乎这将是一个很好的起点。

#8


0  

Not sure if that is want you where looking for? Though all objects in the array need different times to complete the all appear in the order the where submitted to the queue.

不确定是否需要你在哪里寻找?虽然数组中的所有对象都需要不同的时间才能完成,但所有对象都按提交到队列的顺序出现。

typedef int(^SumUpTill)(int);
SumUpTill sum = ^(int max){
    int i = 0;
    int result = 0;
    while (i < max) {
        result += i++;
    }
    return result;
};

dispatch_queue_t queue = dispatch_queue_create("com.dispatch.barrier.async", DISPATCH_QUEUE_CONCURRENT);
NSArray *urlArray = @[  [NSURL URLWithString:@"http://www.google.com"],
                        @"Test",
                        [sum copy],
                        [NSURL URLWithString:@"http://www.apple.com"]
];

[urlArray enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
    dispatch_barrier_async(queue, ^{
        if ([obj isKindOfClass:[NSURL class]]) {
            NSURLRequest *request = [NSURLRequest requestWithURL:obj];
            NSURLResponse *response = nil;
            NSError *error = nil;
            [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
            NSLog(@"index = %d, response=%@ error=%@", idx, response, error);
        }
        else if ([obj isKindOfClass:[NSString class]]) {
            NSLog(@"index = %d, string %@", idx, obj);
        }
        else {
            NSInteger result = ((SumUpTill)obj)(1000000);
            NSLog(@"index = %d, result = %d", idx, result);
        }
    });
}];

#1


4  

Your question immediately made me think of recursion. Turns out, Objective-c blocks can be used in recursion. So I came up with the following solution, which is easy to understand and can scale to N tasks pretty nicely.

你的问题立刻让我想起了递归。事实证明,Objective-c块可用于递归。所以我提出了以下解决方案,这个解决方案很容易理解,可以很好地扩展到N个任务。

// __block declaration of the block makes it possible to call the block from within itself
__block void (^urlFetchBlock)();

// Neatly aggregate all the urls you wish to fetch
NSArray *urlArray = @[
    [NSURL URLWithString:@"http://www.google.com"],
    [NSURL URLWithString:@"http://www.*.com"],
    [NSURL URLWithString:@"http://www.bing.com"],
    [NSURL URLWithString:@"http://www.apple.com"]
];
__block int urlIndex = 0;

// the 'recursive' block 
urlFetchBlock = [^void () {
    if (urlIndex < (int)[urlArray count]){
        [self remoteAPIWithURL:[urlArray objectAtIndex:index] 
            success:^(int theStatus){
                urlIndex++;
                urlFetchBlock();
            }

            failure:^(){
                // handle error. 
            }];
    }
} copy];

// initiate the url requests
urlFetchBlock();

#2


2  

One way to reduce nesting is to define methods that return the individual blocks. In order to facilitate the data sharing which is done "auto-magically" by the Objective C compiler through closures, you would need to define a separate class to hold the shared state.

减少嵌套的一种方法是定义返回单个块的方法。为了便于Objective C编译器通过闭包“自动神奇地”完成数据共享,您需要定义一个单独的类来保存共享状态。

Here is a rough sketch of how this can be done:

这是一个如何做到这一点的草图:

typedef void (^WithStatus)(int);

@interface AsyncHandler : NSObject {
    NSString *_sharedString;
    NSURL *_innerUrl;
    NSURL *_middleUrl;
    WithStatus _innermostBlock;
}
+(void)handleRequest:(WithStatus)innermostBlock
            outerUrl:(NSURL*)outerUrl
            middleUrl:(NSURL*)middleUrl
            innerUrl:(NSURL*)innerUrl;

-(WithStatus)outerBlock;

-(WithStatus)middleBlock;

@end

@implementation AsyncHandler

+(void)handleRequest:(WithStatus)innermostBlock
            outerUrl:(NSURL*)outerUrl
            middleUrl:(NSURL*)middleUrl
            innerUrl:(NSURL*)innerUrl {
    AsyncHandler *h = [[AsyncHandler alloc] init];
    h->_innermostBlock = innermostBlock;
    h->_innerUrl = innerUrl;
    h->_middleUrl = middleUrl;
    [remoteAPIWithURL:outerUrl success:[self outerBlock]];
}

-(WithStatus)outerBlock {
    return ^(int success) {
        _sharedString = [NSString stringWithFormat:@"Outer: %i", success];
        [remoteAPIWithURL:_middleUrl success:[self middleBlock]];
    };
}

-(WithStatus)middleBlock {
    return ^(int success) {
        NSLog("Shared string: %@", _sharedString);
        [remoteAPIWithURL:_innerUrl success:_innermostBlock];
    };
}

@end

Note: All of this assumes ARC; if you are compiling without it, you need to use Block_copy in the methods returning blocks. You would also need to do a copy in the calling code below.

注意:所有这些都假设为ARC;如果您在没有它的情况下进行编译,则需要在返回块的方法中使用Block_copy。您还需要在下面的调用代码中进行复制。

Now your original function can be re-written without the "Russian doll" nesting, like this:

现在您的原始功能可以在没有“俄罗斯娃娃”嵌套的情况下重写,如下所示:

[AsyncHandler
    handleRequest:^(int status){
        //succes!!!
    }
    outerUrl:[NSURL @"http://my.first.url.com"]
    middleUrl:[NSURL @"http://my.second.url.com"]
    innerUrl:[NSURL @"http://my.third.url.com"]
];

#3


2  

Iterative algorithm:

  • Create a __block variable (int urlNum) to keep track of the current URL (inside an NSArray of them).
  • 创建一个__block变量(int urlNum)来跟踪当前URL(在它们的NSArray中)。

  • Have the onUrlComplete block fire off the next request until all URLs have been loaded.
  • 让onUrlComplete块触发下一个请求,直到所有URL都被加载。

  • Fire the first request.
  • 解雇第一个请求。

  • When all URLs have been loaded, do the "//success!" dance.
  • 加载完所有网址后,请执行“// success!”舞蹈。

Code written without the aid of XCode (meaning, there may be compiler errors -- will fix if necessary):

在没有XCode的帮助下编写的代码(意思是,可能存在编译器错误 - 必要时将修复):

- (void)loadUrlsAsynchronouslyIterative:(NSArray *)urls {
  __block int urlNum = 0;
  void(^onUrlComplete)(int) = nil; //I don't remember if you can call a block from inside itself.
  onUrlComplete = ^(int status) {
    if (urlNum < urls.count) {
      id nextUrl = urls[urlNum++];
      [remoteAPIWithURL:nextUrl success:onUrlComplete];
    } else {
      //success!
    }
  }
  onUrlComplete(0); //fire first request
}

Recursive algorithm:

  • Create a method to load all the remaining URLs.
  • 创建一个方法来加载所有剩余的URL。

  • When remaining URLs is empty, fire "onSuccess".
  • 当剩余的URL为空时,触发“onSuccess”。

  • Otherwise, fire request for the next URL and provide a completion block that recursively calls the method with all but the first remaining URLs.
  • 否则,触发下一个URL的请求并提供一个完成块,该块以递归方式调用除了第一个剩余URL之外的所有URL。

  • Complications: we declared the "onSuccess" block to accept an int status parameter, so we pass the last status variable down (including a "default" value).
  • 并发症:我们声明“onSuccess”块接受一个int状态参数,因此我们将最后一个状态变量向下传递(包括“默认”值)。

Code written without the aid of XCode (bug disclaimer here):

没有XCode帮助编写的代码(这里的bug免责声明):

- (void)loadUrlsAsynchronouslyRecursive:(NSArray *)remainingUrls onSuccess:(void(^)(int status))onSuccess lastStatus:(int)lastStatus {
  if (remainingUrls.count == 0) {
    onSuccess(lastStatus);
    return;
  }
  id nextUrl = remainingUrls[0];
  remainingUrls = [remainingUrls subarrayWithRange:NSMakeRange(1, remainingUrls.count-1)];
  [remoteAPIWithUrl:nextUrl onSuccess:^(int status) {
    [self loadUrlsAsynchronouslyRecursive:remainingUrls onSuccess:onSuccess lastStatus:status];
  }];
}

//fire first request:
[self loadUrlsAsynchronouslyRecursive:urls onSuccess:^(int status) {
  //success here!
} lastStatus:0];

Which is better?

哪个更好?

  • The iterative algorithm is simple and concise -- if you're comfortable playing games with __block variables and scopes.
  • 迭代算法简单明了 - 如果你习惯用__block变量和范围玩游戏。

  • Alternatively, the recursive algorithm doesn't require __block variables and is fairly simple, as recursive algorithms go.
  • 或者,递归算法不需要__block变量,并且非常简单,就像递归算法一样。

  • The recursive implementation is more re-usable that the iterative one (as implemented).
  • 递归实现比迭代实现更具可重用性(如实现的那样)。

  • The recursive algorithm might leak (it requires a reference to self), but there are several ways to fix that: make it a function, use __weak id weakSelf = self;, etc.
  • 递归算法可能会泄漏(它需要引用self),但有几种方法可以解决这个问题:使它成为一个函数,使用__weak id weakSelf = self;等。

How easy would it be to add error-handling?

添加错误处理有多容易?

  • The iterative implementation can easily be extended to check the value of status, at the cost of the onUrlComplete block becoming more complex.
  • 迭代实现可以很容易地扩展以检查状态的值,代价是onUrlComplete块变得更加复杂。

  • The recursive implementation is perhaps not as straight-forward to extend -- primarily because it is re-usable. Do you want to cancel loading more URLs when the status is such-and-such? Then pass down a status-checking/error-handling block that accepts int status and returns BOOL (for example YES to continue, NO to cancel). Or perhaps modify onSuccess to accept both int status and NSArray *remainingUrls -- but you'll need to call loadUrlsAsynchronouslyRecursive... in your onSuccess block implementation.
  • 递归实现可能不是直接扩展 - 主要是因为它是可重用的。当状态如此时,是否要取消加载更多URL?然后传递接受int状态并返回BOOL的状态检查/错误处理块(例如,YES继续,NO取消)。或者修改onSuccess以接受int status和NSArray * remainingUrls - 但是你需要在onSuccess块实现中调用loadUrlsAsynchronouslyRecursive ....

#4


1  

You said (in a comment), “asynchronous methods offer easy asynchronisity without using explicit threads.” But your complaint seems to be that you're trying to do something with asynchronous methods, and it's not easy. Do you see the contradiction here?

你说(在评论中),“异步方法提供了简单的异步性,而不使用显式线程。”但你的抱怨似乎是你试图用异步方法做某事,这并不容易。你看到这里的矛盾吗?

When you use a callback-based design, you sacrifice the ability to express your control flow directly using the language's built-in structures.

当您使用基于回调的设计时,您会牺牲使用语言的内置结构直接表达控制流的能力。

So I suggest you stop using a callback-based design. Grand Central Dispatch (GCD) makes it easy (that word again!) to perform work “in the background”, and then call back to the main thread to update the user interface. So if you have a synchronous version of your API, just use it in a background queue:

所以我建议你停止使用基于回调的设计。 Grand Central Dispatch(GCD)可以轻松(再次说明这个词!)在“后台”执行工作,然后回调主线程来更新用户界面。因此,如果您有API的同步版本,只需在后台队列中使用它:

- (void)interactWithRemoteAPI:(id<RemoteAPI>)remoteAPI {
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        // This block runs on a background queue, so it doesn't block the main thread.
        // But it can't touch the user interface.

        for (NSURL *url in @[url1, url2, url3, url4]) {
            int status = [remoteAPI syncRequestWithURL:url];
            if (status != 0) {
                dispatch_async(dispatch_get_main_queue(), ^{
                    // This block runs on the main thread, so it can update the
                    // user interface.
                    [self remoteRequestFailedWithURL:url status:status];
                });
                return;
            }
        }
    });
}

Since we're just using normal control flow, it's straightforward to do more complicated things. Say we need to issue two requests, then upload a file in chunks of at most 100k, then issue one more request:

由于我们只是使用正常的控制流程,因此可以直接执行更复杂的操作。假设我们需要发出两个请求,然后以最多100k的块为单位上传文件,然后再发出一个请求:

#define AsyncToMain(Block) dispatch_async(dispatch_get_main_queue(), Block)

- (void)uploadFile:(NSFileHandle *)fileHandle withRemoteAPI:(id<RemoteAPI>)remoteAPI {
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        int status = [remoteAPI syncRequestWithURL:url1];
        if (status != 0) {
            AsyncToMain(^{ [self remoteRequestFailedWithURL:url1 status:status]; });
            return;
        }

        status = [remoteAPI syncRequestWithURL:url2];
        if (status != 0) {
            AsyncToMain(^{ [self remoteRequestFailedWithURL:url2 status:status]; });
            return;
        }

        while (1) {
            // Manage an autorelease pool to avoid accumulating all of the
            // 100k chunks in memory simultaneously.
            @autoreleasepool {
                NSData *chunk = [fileHandle readDataOfLength:100 * 1024];
                if (chunk.length == 0)
                    break;
                status = [remoteAPI syncUploadChunk:chunk];
                if (status != 0) {
                    AsyncToMain(^{ [self sendChunkFailedWithStatus:status]; });
                    return;
                }
            }
        }

        status = [remoteAPI syncRequestWithURL:url4];
        if (status != 0) {
            AsyncToMain(^{ [self remoteRequestFailedWithURL:url4 status:status]; });
            return;
        }

        AsyncToMain(^{ [self uploadFileSucceeded]; });
    });
}

Now I'm sure you're saying “Oh yeah, that looks great.” ;^) But you might also be saying “What if RemoteAPI only has asynchronous methods, not synchronous methods?”

现在我确定你会说“哦,是的,看起来很棒。”; ^)但你也可能会说“如果RemoteAPI只有异步方法,而不是同步方法怎么办?”

We can use GCD to create a synchronous wrapper for an asynchronous method. We need to make the wrapper call the async method, then block until the async method calls the callback. The tricky bit is that perhaps we don't know which queue the async method uses to invoke the callback, and we don't know if it uses dispatch_sync to call the callback. So let's be safe by calling the async method from a concurrent queue.

我们可以使用GCD为异步方法创建同步包装器。我们需要让包装器调用异步方法,然后阻塞,直到异步方法调用回调。棘手的一点是,我们可能不知道异步方法使用哪个队列来调用回调,我们不知道它是否使用dispatch_sync来调用回调。因此,通过从并发队列调用异步方法来确保安全。

- (int)syncRequestWithRemoteAPI:(id<RemoteAPI>)remoteAPI url:(NSURL *)url {
    __block int outerStatus;
    dispatch_semaphore_t sem = dispatch_semaphore_create(0);
    [remoteAPI asyncRequestWithURL:url completion:^(int status) {
        outerStatus = status;
        dispatch_semaphore_signal(sem);
    }];
    dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
    dispatch_release(sem);
    return outerStatus;
}

UPDATE

I will respond to your third comment first, and your second comment second.

我将首先回复您的第三条评论,然后再回复您的第二条评论。

Third Comment

Your third comment:

你的第三个评论:

Last but not least, your solution of dedicating a separate thread to wrap around the synchronous version of a call is more costly than using the async alternatives. a Thread is an expensive resource, and when it is blocking you basically have lost one thread. Async calls (the ones in the OS libraries at least) are typically handled in a much more efficient way. (For instance, if you would request 10 urls at the same time, chances are it will not spin up 10 threads (or put them in a threadpool))

最后但同样重要的是,您使用单独的线程来绕过调用的同步版本的解决方案比使用异步备选方案更昂贵。一个线程是一个昂贵的资源,当它阻塞你基本上丢失了一个线程。异步调用(至少是OS库中的调用)通常以更有效的方式处理。 (例如,如果您同时请求10个网址,则可能不会启动10个线程(或将它们放入线程池中))

Yes, using a thread is more expensive than just using the asynchronous call. So what? The question is whether it's too expensive. Objective-C messages are too expensive in some scenarios on current iOS hardware (the inner loops of a real-time face detection or speech recognition algorithm, for example), but I have no qualms about using them most of the time.

是的,使用线程比使用异步调用更昂贵。所以呢?问题是它是否过于昂贵。 Objective-C消息在当前iOS硬件(例如,实时面部检测或语音识别算法的内部循环)的某些情况下过于昂贵,但我对大多数时间使用它们没有任何疑虑。

Whether a thread is “an expensive resource” really depends on the context. Let's consider your example: “For instance, if you would request 10 urls at the same time, chances are it will not spin up 10 threads (or put them in a threadpool)”. Let's find out.

线程是否是“昂贵的资源”实际上取决于上下文。让我们考虑你的例子:“例如,如果你同时请求10个网址,很可能不会启动10个线程(或将它们放在线程池中)”。让我们来看看。

NSURL *url = [NSURL URLWithString:@"http://1.1.1.1/"];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
for (int i = 0; i < 10; ++i) {
    [NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
        NSLog(@"response=%@ error=%@", response, error);
    }];
}

So here I am using Apple's own recommended +[NSURLConnection sendAsynchronousRequest:queue:completionHandler:] method to send 10 requests asynchronously. I've chosen the URL to be non-responsive, so I can see exactly what kind of thread/queue strategy Apple uses to implement this method. I ran the app on my iPhone 4S running iOS 6.0.1, paused in the debugger, and took a screen shot of the Thread Navigator:

所以我在这里使用Apple自己推荐的+ [NSURLConnection sendAsynchronousRequest:queue:completionHandler:]方法异步发送10个请求。我选择了URL无响应,因此我可以确切地看到Apple使用什么样的线程/队列策略来实现此方法。我在运行iOS 6.0.1的iPhone 4S上运行应用程序,在调试器中暂停,并拍摄了Thread Navigator的屏幕截图:

如何使用Objective-C中的块进行结构化编程

You can see that there are 10 threads labeled com.apple.root.default-priority. I've opened three of them so you can see that they are just normal GCD queue threads. Each calls a block defined in +[NSURLConnection sendAsynchronousRequest:…], which just turns around and calls +[NSURLConnection sendSynchronousRequest:…]. I checked all 10, and they all have the same stack trace. So, in fact, the OS library does spin up 10 threads.

您可以看到有10个线程标记为com.apple.root.default-priority。我打开了其中三个,这样你就可以看到它们只是普通的GCD队列线程。每个调用一个在+ [NSURLConnection sendAsynchronousRequest:...]中定义的块,它只是转身并调用+ [NSURLConnection sendSynchronousRequest:...]。我检查了所有10个,它们都有相同的堆栈跟踪。因此,事实上,OS库确实启动了10个线程。

I bumped the loop count from 10 to 100 and found that GCD caps the number of com.apple.root.default-priority threads at 64. So my guess is the other 36 requests I issued are queued up in the global default-priority queue, and won't even start executing until some of the 64 “running” requests finish.

我将循环计数从10增加到100,并发现GCD在64处限制了com.apple.root.default优先级线程的数量。所以我的猜测是我发出的其他36个请求在全局默认优先级队列中排队,甚至在64个“运行”请求中的某些请求完成后才会开始执行。

So, is it too expensive to use a thread to turn an asynchronous function into a synchronous function? I'd say it depends on how many of these you plan to do simultaneously. I would have no qualms if the number's under 10, or even 20.

那么,使用线程将异步函数转换为同步函数是否过于昂贵?我会说这取决于你打算同时做多少这些。如果数字低于10,甚至20,我都不会有任何疑虑。

Second Comment

Which brings me to your second comment:

这让我想到你的第二个评论:

However, when you have: do these 3 things at the same time, and when 'any' of them is finished then ignore the rest and do these 3 calls at the same time and when 'all' of them finish then succes.

然而,当你有:同时做这三件事,当'任何'完成后,然后忽略其余的,同时做这3个电话,当'all'完成然后成功。

These are cases where it's easy to use GCD, but we can certainly combine the GCD and async approaches to use fewer threads if you want, while still using the languages native tools for control flow.

这些是易于使用GCD的情况,但我们当然可以将GCD和异步方法结合使用,以便在需要时使用更少的线程,同时仍然使用语言本机工具来控制流。

First, we'll make a typedef for the remote API completion block, just to save typing later:

首先,我们将为远程API完成块创建一个typedef,以便稍后保存输入:

typedef void (^RemoteAPICompletionBlock)(int status);

I'll start the control flow the same way as before, by moving it off the main thread to a concurrent queue:

我将以与以前相同的方式启动控制流,将其从主线程移动到并发队列:

- (void)complexFlowWithRemoteAPI:(id<RemoteAPI>)remoteAPI {
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

First we want to issue three requests simultaneously and wait for one of them to succeed (or, presumably, for all three to fail).

首先,我们要同时发出三个请求并等待其中一个请求成功(或者,假设所有三个请求都失败)。

So let's say we have a function, statusOfFirstRequestToSucceed, that issues any number of asynchronous remote API requests and waits for the first to succeed. This function will provide the completion block for each async request. But the different requests might take different arguments… how can we pass the API requests to the function?

因此,假设我们有一个函数statusOfFirstRequestToSucceed,它发出任意数量的异步远程API请求并等待第一个成功。此函数将为每个异步请求提供完成块。但是不同的请求可能会采用不同的论点......我们如何将API请求传递给函数?

We can do it by passing a literal block for each API request. Each literal block takes the completion block and issues the asynchronous remote API request:

我们可以通过为每个API请求传递一个文字块来实现。每个文字块都使用完成块并发出异步远程API请求:

        int status = statusOfFirstRequestToSucceed(@[
            ^(RemoteAPICompletionBlock completion) {
                [remoteAPI requestWithCompletion:completion];
            },
            ^(RemoteAPICompletionBlock completion) {
                [remoteAPI anotherRequestWithCompletion:completion];
            },
            ^(RemoteAPICompletionBlock completion) {
                [remoteAPI thirdRequestWithCompletion:completion];
            }
        ]);
        if (status != 0) {
            AsyncToMain(^{ [self complexFlowFailedOnFirstRoundWithStatus:status]; });
            return;
        }

OK, now we've issued the three first parallel requests and waited for one to succeed, or for all of them to fail. Now we want to issue three more parallel requests and wait for all to succeed, or for one of them to fail. So it's almost identical, except I'm going to assume a function statusOfFirstRequestToFail:

好的,现在我们已经发布了三个第一个并行请求并等待一个成功,或者所有这些请求都失败了。现在我们要发出三个并行请求并等待所有成功,或者其中一个请求失败。所以它几乎完全相同,除了我将假设一个函数statusOfFirstRequestToFail:

        status = statusOfFirstRequestToFail(@[
            ^(RemoteAPICompletionBlock completion) {
                [remoteAPI requestWithCompletion:completion];
            },
            ^(RemoteAPICompletionBlock completion) {
                [remoteAPI anotherRequestWithCompletion:completion];
            },
            ^(RemoteAPICompletionBlock completion) {
                [remoteAPI thirdRequestWithCompletion:completion];
            }
        ]);
        if (status != 0) {
            AsyncToMain(^{ [self complexFlowFailedOnSecondRoundWithStatus:status]; });
            return;
        }

Now both rounds of parallel requests have finished, so we can notify the main thread of success:

现在两轮并行请求都已完成,因此我们可以通知成功的主线程:

        [self complexFlowSucceeded];
    });
}

Overall, that seems like a pretty straightforward flow of control to me, and we just need to implement statusOfFirstRequestToSucceed and statusOfFirstRequestToFail. We can implement them with no extra threads. Since they are so similar, we'll make them both call on a helper function that does the real work:

总的来说,这似乎是一个非常直接的控制流程,我们只需要实现statusOfFirstRequestToSucceed和statusOfFirstRequestToFail。我们可以实现它们而不需要额外的线程。因为它们非常相似,所以我们会让它们都调用辅助函数来完成真正的工作:

static int statusOfFirstRequestToSucceed(NSArray *requestBlocks) {
    return statusOfFirstRequestWithStatusPassingTest(requestBlocks, ^BOOL (int status) {
        return status == 0;
    });
}

static int statusOfFirstRequestToFail(NSArray *requestBlocks) {
    return statusOfFirstRequestWithStatusPassingTest(requestBlocks, ^BOOL (int status) {
        return status != 0;
    });
}

In the helper function, I'll need a queue in which to run the completion blocks, to prevent race conditions:

在辅助函数中,我需要一个队列来运行完成块,以防止竞争条件:

static int statusOfFirstRequestWithStatusPassingTest(NSArray *requestBlocks,
    BOOL (^statusTest)(int status))
{
    dispatch_queue_t completionQueue = dispatch_queue_create("remote API completion", 0);

Note that I will only put blocks on completionQueue using dispatch_sync, and dispatch_sync always runs the block on the current thread unless the queue is the main queue.

请注意,我将仅使用dispatch_sync将块放在completionQueue上,并且dispatch_sync始终在当前线程上运行块,除非队列是主队列。

I'll also need a semaphore, to wake up the outer function when some request has completed with a passing status, or when all requests have finished:

我还需要一个信号量,当某个请求以传递状态完成时唤醒外部函数,或者所有请求都已完成时:

    dispatch_semaphore_t enoughJobsCompleteSemaphore = dispatch_semaphore_create(0);

I'll keep track of the number of jobs not yet finished and the status of the last job to finish:

我将跟踪尚未完成的工作数量以及最后完成的工作的状态:

    __block int jobsLeft = requestBlocks.count;
    __block int outerStatus = 0;

When jobsLeft becomes 0, it means that either I've set outerStatus to a status that passes the test, or that all jobs have completed. Here's the completion block where I'll the work of tracking whether I'm done waiting. I do it all on completionQueue to serialize access to jobsLeft and outerStatus, in case the remote API dispatches multiple completion blocks in parallel (on separate threads or on a concurrent queue):

当jobsLeft变为0时,表示我将outerStatus设置为通过测试的状态,或者所有作业都已完成。这是完成块,我将跟踪我是否已经完成等待。我在completionQueue上全部执行以序列化对jobsLeft和outerStatus的访问,以防远程API并行调度多个完成块(在单独的线程或并发队列上):

    RemoteAPICompletionBlock completionBlock = ^(int status) {
        dispatch_sync(completionQueue, ^{

I check to see if the outer function is still waiting for the current job to complete:

我检查外部函数是否仍在等待当前作业完成:

            if (jobsLeft == 0) {
                // The outer function has already returned.
                return;
            }

Next, I decrement the number of jobs remaining and make the completed job's status available to the outer function:

接下来,我减少剩余的作业数量,并将完成的作业状态提供给外部函数:

            --jobsLeft;
            outerStatus = status;

If the completed job's status passes the test, I set jobsLeft to zero to prevent other jobs from overwriting my status or singling the outer function:

如果已完成作业的状态通过测试,我将jobsLeft设置为零以防止其他作业覆盖我的状态或单独使用外部函数:

            if (statusTest(status)) {
                // We have a winner.  Prevent other jobs from overwriting my status.
                jobsLeft = 0;
            }

If there are no jobs left to wait on (because they've all finished or because this job's status passed the test), I wake up the outer function:

如果没有剩余的工作要等待(因为他们已经完成或因为这个工作的状态通过了测试),我唤醒了外部功能:

            if (jobsLeft == 0) {
                dispatch_semaphore_signal(enoughJobsCompleteSemaphore);
            }

Finally, I release the queue and the semaphore. (The retains will be later, when I loop through the request blocks to execute them.)

最后,我释放队列和信号量。 (当我遍历请求块以执行它们时,保留将会更晚。)

            dispatch_release(completionQueue);
            dispatch_release(enoughJobsCompleteSemaphore);
        });
    };

That's the end of the completion block. The rest of the function is trivial. First I execute each request block, and I retain the queue and the semaphore to prevent dangling references:

那是完成块的结束。其余的功能是微不足道的。首先,我执行每个请求块,并保留队列和信号量以防止悬空引用:

    for (void (^requestBlock)(RemoteAPICompletionBlock) in requestBlocks) {
        dispatch_retain(completionQueue); // balanced in completionBlock
        dispatch_retain(enoughJobsCompleteSemaphore); // balanced in completionBlock
        requestBlock(completionBlock);
    }

Note that the retains aren't necessary if you're using ARC and your deployment target is iOS 6.0 or later.

请注意,如果您使用ARC且部署目标是iOS 6.0或更高版本,则不需要保留。

Then I just wait for one of the jobs to wake me up, release the queue and the semaphore, and return the status of the job that woke me:

然后我只是等待其中一个工作唤醒我,释放队列和信号量,并返回唤醒我的工作状态:

    dispatch_semaphore_wait(enoughJobsCompleteSemaphore, DISPATCH_TIME_FOREVER);
    dispatch_release(completionQueue);
    dispatch_release(enoughJobsCompleteSemaphore);
    return outerStatus;
}

Note that the structure of statusOfFirstRequestWithStatusPassingTest is fairly generic: you can pass any request blocks you want, as long as each one calls the completion block and passes in an int status. You could modify the function to handle a more complex result from each request block, or to cancel outstanding requests (if you have a cancellation API).

请注意,statusOfFirstRequestWithStatusPassingTest的结构非常通用:您可以传递所需的任何请求块,只要每个请求块都调用完成块并传入int状态。您可以修改函数以处理来自每个请求块的更复杂的结果,或者取消未完成的请求(如果您有取消API)。

#5


0  

While researching this myself I bumped into a port of Reactive Extensions to Objective-C. Reactive Extensions is like having the ability to querying a set of events or asynchronous operations. I know it has had a big uptake under .Net and JavaScript, and now apparently there is a port for Objective-C as well

在自己研究这个问题时,我碰到了一个针对Objective-C的Reactive Extensions端口。 Reactive Extensions就像具有查询一组事件或异步操作的能力。我知道它在.Net和JavaScript下已经有了很大的发展,现在显然还有一个Objective-C的端口

https://github.com/blog/1107-reactivecocoa-for-a-better-world

Syntax looks tricky. I wonder if there is real world experience with it for iPhone development and if it does actually solve this issue elegantly.

语法看起来很棘手。我想知道iPhone开发是否有真实的世界经验,如果它确实优雅地解决了这个问题。

#6


0  

I tend to wrap big nested block cluster f**** like you describe in subclasses of NSOperation that describe what the overall behaviour that your big nest block cluster f*** is actually doing (rather than leaving them littered throughout other code).

我倾向于包装大型嵌套块集群f ****,就像你在NSOperation的子类中描述的那样,它描述了你的大嵌套块f ***实际上正在做的整体行为(而不是让它们在其他代码中散落)。

For example if your following code:

例如,如果您的代码如下:

[remoteAPIWithURL:url1 success:^(int status){
    [remoteAPIWithURL:url2 success:^(int status){
        [remoteAPIWithURL:url3 success:^(int status){
            [remoteAPIWithURL:url2 success:^(int status){
            //succes!!!
            }];
        }];
    }];
}];

is intended to get an authorise token and then sync something perhaps it would be an NSAuthorizedSyncOperation… I'm sure you get the gist. Benefits of this are nice tidy bundles of behaviour wrapped up in a class with one place to edit them if things change down the line. My 2¢.

旨在获取授权令牌,然后同步一些可能是NSAuthorizedSyncOperation ...我相信你得到了要点。这样做的好处是在一个类中包含了很好的整洁行为,如果事情发生变化,就会在一个地方编辑它们。我的2¢。

#7


0  

In NSDocument the following methods are available for serialization:

在NSDocument中,以下方法可用于序列化:

Serialization
– continueActivityUsingBlock:
– continueAsynchronousWorkOnMainThreadUsingBlock:
– performActivityWithSynchronousWaiting:usingBlock:
– performAsynchronousFileAccessUsingBlock:
– performSynchronousFileAccessUsingBlock:

I'm just digging into this, but it seems like this would be a good place to start.

我只是深入研究这一点,但似乎这将是一个很好的起点。

#8


0  

Not sure if that is want you where looking for? Though all objects in the array need different times to complete the all appear in the order the where submitted to the queue.

不确定是否需要你在哪里寻找?虽然数组中的所有对象都需要不同的时间才能完成,但所有对象都按提交到队列的顺序出现。

typedef int(^SumUpTill)(int);
SumUpTill sum = ^(int max){
    int i = 0;
    int result = 0;
    while (i < max) {
        result += i++;
    }
    return result;
};

dispatch_queue_t queue = dispatch_queue_create("com.dispatch.barrier.async", DISPATCH_QUEUE_CONCURRENT);
NSArray *urlArray = @[  [NSURL URLWithString:@"http://www.google.com"],
                        @"Test",
                        [sum copy],
                        [NSURL URLWithString:@"http://www.apple.com"]
];

[urlArray enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
    dispatch_barrier_async(queue, ^{
        if ([obj isKindOfClass:[NSURL class]]) {
            NSURLRequest *request = [NSURLRequest requestWithURL:obj];
            NSURLResponse *response = nil;
            NSError *error = nil;
            [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
            NSLog(@"index = %d, response=%@ error=%@", idx, response, error);
        }
        else if ([obj isKindOfClass:[NSString class]]) {
            NSLog(@"index = %d, string %@", idx, obj);
        }
        else {
            NSInteger result = ((SumUpTill)obj)(1000000);
            NSLog(@"index = %d, result = %d", idx, result);
        }
    });
}];