How to do structured programming using blocks in Objective-C
Asked Answered
P

8

10

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?

Preface answered 9/11, 2012 at 14:14 Comment(7)
Why does any of that have to be nested? Why not do it sequentially?Misadventure
Because the code in the block is called asynchronously. Thats why you submit a 'block' which is called at a later stage (in this case when the request is finished). So blocks and asynchrony are great, but I wonder how to organize it correctly without losing structurePreface
I would say use some different mechanism, like a background thread with a work queue. I agree that the nesting is messy.Misadventure
True, but asynchronous methods offer easy asynchronisity without using explicit threads. I'm just wondering how to use them correctlyPreface
Do the url depend on each other or influence the status received further processing. If the answer is NO, you could try to call the latest URL first and use dispatch_barrier_async calls so you can be sure you keep calls to remoteAPI... in order but have asynchronous execution.Past
@BerndRabe The example is just an example. Sometimes they are dependent, sometimes they are not.Preface
I don't know who to give the bounty to. Most answers seem to have good points although none of them seem to have the golden method of how to deal with asynchronicity on IOS in a clean and maintainable way without getting a spaghetti mix of blocks, semaphores, continuations, etc I'm awarding the bounty to the top answer at this moment and want to thank everyone for their input. I hope that in the future some kind of library or language extension will come which makes dealing with this a lot easier.Preface
C
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.

// __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();
Conceal answered 20/11, 2012 at 12:56 Comment(3)
declaring the block and then being able to call it recursively is a clever trick indeed.Preface
do you mean "urlIndex" instead of "index" in [urlArray objectAtIndex:index]Linea
@AshishPisey, No; urlArray is an NSArray, and does not have a urlIndex method.Conceal
I
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.

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.

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"]
];
Illuminant answered 13/11, 2012 at 23:0 Comment(3)
I did not try any of it, so there is a good chance that this code would not compile. If that is the case, please let me know, and I'll try fixing it.Illuminant
Although I agree this solves the particular example of me fetching 3 urls, it would need a new class for fetching 4 or 5 urls, or any other intricate async flow. I do like that you've encapsulated the complexity in an easy to use class thoughPreface
@Preface Correct, this approach is usable to encapsulate one particular flow, and does not offer a framework-like ability to compose flows arbitrarily. The idea was to make your asynchronous code more manageable while keeping it understandable without much prior knowledge.Illuminant
I
2

Iterative algorithm:

  • Create a __block variable (int urlNum) to keep track of the current URL (inside an NSArray of them).
  • Have the onUrlComplete block fire off the next request until all URLs have been loaded.
  • Fire the first request.
  • When all URLs have been loaded, do the "//success!" dance.

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

- (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.
  • When remaining URLs is empty, fire "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.
  • Complications: we declared the "onSuccess" block to accept an int status parameter, so we pass the last status variable down (including a "default" value).

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

- (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.
  • Alternatively, the recursive algorithm doesn't require __block variables and is fairly simple, as recursive algorithms go.
  • 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.

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.
  • 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.
Inapt answered 17/11, 2012 at 18:12 Comment(1)
Although I like the way you split it into 2 ways. I still see this becoming very unreadable very quickly with either method. Just collecting 4 urls is one of the more simple examples, and the code already looks inside out. Just imagine a slightly more complicated goal, and the blocks, callbacks, state and parameters which need to be passed around grow quickly out of controlPreface
A
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:

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

#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?”

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.

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

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.

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.

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:

10 NSURLConnection sendAsynchronousRequest: threads

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.

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.

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.

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.

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.

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

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?

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:

        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:

        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:

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.

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

    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:

            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.

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

Atrium answered 17/11, 2012 at 20:32 Comment(6)
About your 'quote'. Yes blocks do offer easy asynchronicity for simple calls. But the moment you have to orchestrate them they become very tricky. I do see the advantages, I was just wondering what the correct patterns are that people use.Preface
About your proposed solution: 'just use the synchronous versions of the calls and wrap them all in a thread' (which is what using GCD basically means). This is great for when you have: 'do this, and then that and then that and then that' kind of sequences which are very imperative. 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. So non-trivial control flows.Preface
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))Preface
@Preface I have responded to your comments by extending my answer.Atrium
I stand correct about the spinning up of threads. I'm probably too used to how things are in .Net land where socket operations are not handled on a per thread bases, but share a common thread (socket overlapped io). This is generally much more scalable. Then again, since IOS is 'just' a mobile OS, this kind of optimization is perhaps completely unnecessary.Preface
about your update about the second comment: wow, thats a lot of code which is hardly trivial. I think if this is really the way one has to work with blocks (so queues, semaphores, race conditions, etc) than Apple really needs to come up with something to ease the development process and encapsulate all the threading primitives needed for basic control flow.Preface
P
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

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.

Preface answered 14/11, 2012 at 9:37 Comment(0)
P
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).

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

Photomural answered 18/11, 2012 at 20:41 Comment(1)
so essentially I agree with @dasblinkenlight :)Photomural
P
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);
        }
    });
}];
Past answered 20/11, 2012 at 15:20 Comment(0)
F
0

In NSDocument the following methods are available for serialization:

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.

Feather answered 20/11, 2012 at 15:38 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.