Question

I have an app wherein I need to loop through and load stored data in order to generate information to save to PDF file, pages. I created a loop, but it runs too fast, so to speak. The saved pages cannot load fast enough, so the resulting data is incomplete. I think I need to create a for loop with delay or completion handler. Something like:

for (int i = 0, i < numberOfPages, doTaskWithCompletion:i++) {
    arrayOfPages = [self loadDataAtIndex:i withCompletionHandler:handler];
    [self writeDataToFile:arrayOfPages];
}

Not even sure how to write that pseudo-code, but basically I only want to jump to the next iteration after a completion handler has fired for the task of loading the data.

Note: Source data comes from core data, saved as PDF file. The data is pages and views for page. Each page can have 1 or unlimited views. Each view might have data too, like an image. What happens currently is that the number of pages written is correct, but the page's views that are rendered to PDF are identical, because they all read the same data, as the pages are not loaded before the PDF is written.

Was it helpful?

Solution

You may approach the problem as follows:

First define a generic completion block type. Note, param result can be anything. If it's an NSError this signals a failure, otherwise success.

typedef void (^completion_t)(id result);

Define a block type for your asynchronous task. Your task takes an index as input, and - since it is asynchronous, it takes a completion block as last parameter:

typedef void (^unary_async_t)(int index, completion_t completion);

Your task block object can be defined as follows:

unary_async_t task = ^(int index, completion_t completion) {
    [self loadDataAtIndex:index withCompletion:^(id result){
        if (![result isKindOfClass:[NSError class]]) {
            [self writeDataToFile:result];
            result = @"OK";
        }
        if (completion) {
            completion(result);
        }
    }];
};

Note: your loadDataAtIndex:withCompletion: is asynchronous, and thus, it takes a completion block as last parameter. The completion block's parameter result is the result of the asynchronous task, namely the "array of pages" - or an NSError object if that fails.

In this completion block, you safe your result (the pages) to disk, invoking writeDataToFile:. (We assuming this won't fail). If that is all finished, task invokes the provided completion block completion (if not nil) passing the result of the whole operation, which is @"OK" in case of success, and an NSError in case of failure.

Now, the more interesting part: how to make this in a loop, where numerous _task_s will be executed sequentially, one after the other:

We define two helper methods - or functions:

What we finally need is a function with this signature:

void apply_each_with_range(int lowerBound, int upperBound, 
                           unary_async_t task, completion_t completion);

This is the function which shall invoke task N times, passing it parameter index in the range from lowerBound (including) to upperBound (not including), where N equals upperBound - lowerBound and index starts with lowerBound.

It's an asynchronous function, and thus, takes a completion block as last parameter. Do I repeat myself? Well, you should recognize the pattern! ;)

Here is the implementation:

void apply_each_with_range(int lowerBound, int upperBound, 
                           unary_async_t task, completion_t completion)
{
    do_apply(lowerBound, upperBound, task, completion);
}

And the other helper - which is finally performing some sort of "for_each index in range[upperBound, lowerBound] sequentially invoke task with parameter index":

static void do_apply(int index, int upperBound, 
                     unary_async_t task, completion_t completion)
{
    if (index >= upperBound) {
        if (completion)
            completion(@"OK");
        return;
    }
    task(index, ^(id result) {
        if (![result isKindOfClass:[NSError class]]) {
            do_apply(index + 1, upperBound, task, completion);
        }
        else {
            // error occurred: stop iterating and signal error
            if (completion) {
                completion(result);
            }
        }
    });
}

The function do_apply first checks whether the index is out of range. If, then it is finished and calls the completion handler with an @"OK". Otherwise, it invokes task with argument index and provides a completion handler which gets invoked when task finished, which itself passes the result of the task. If that was successful, do_apply will invoke itself with argument index incremented by one. This may look like a "recursion" - but it is not. do_apply already returned, when it invokes itself.

If task returned and error, do_apply stops, "returning" the error from task in its completion handler (which is finally provided by the call-site).

Now, you just need to put these pieces together in your project - which should be fairly easy.

OTHER TIPS

You can test to see if the data is saved and set a flag to see whether or not the data is saved. Then put an if statement like if the flag is set to yes then run the for loop. You can do something like that for all of your data to make sure all data is stored. Good luck!! Hope this helps!!

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top