Search code examples
objective-clambdaobjective-c-blockscontinuationsstructured-programming

How to do structured programming using blocks in Objective-C


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:

[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:

- (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.

What is the best practice in Objective C?


Solution

  • 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.

    // __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.stackoverflow.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();