Shocks
Shocks

Reputation: 808

AFNetworking Completion Block

In my current design I have a tableview which represents a folder structure. When the user taps on a cell an AFHTTPRequestOperation is created to download a file. The file is downloaded while the cell currently shows the current download state: none/downloaded/downloading.

The download state is set on an NSManagedObject which corresponds to each cell. In my completion block for the download, I set the download state to the "downloaded" flag. The problem with this is if the user navigates away from the the current tableview data and to another, the competition block will set the wrong NSManagedObject - it does the look-up based on the NSIndex.

In a nutshell I would like to know how I can pass a object along with my AFHTTPRequestOperation so that on completion an operation can be performed on it. It could be as simple as an int or string and I can do an NSPredicate request based on this value.

AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
        [self.operations addObject:operation];
        [self.inProgressDownloads addObject:indexPath];
        NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
        NSString *extension = [[self.selectedFile.name componentsSeparatedByString:@"."] lastObject];
        NSString *path = [[paths objectAtIndex:0] stringByAppendingPathComponent:[NSString stringWithFormat: @"%@.%@", self.selectedFile.item_id, extension]];
        operation.outputStream = [NSOutputStream outputStreamToFileAtPath:path append:NO];
        self.selectedFile.status = DOWNLOADING;
        [self.tableView beginUpdates];
        [self.tableView reloadRowsAtIndexPaths:[NSArray arrayWithObjects:indexPath, nil] withRowAnimation:UITableViewRowAnimationNone];
        [self.tableView endUpdates];

        [operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
            NSLog(@"File downloaded to: %@", path);
            self.selectedFile.status = DOWNLOADED;
            self.selectedFile.is_stored = @YES;
            [self.tableView beginUpdates];
            [self.tableView reloadRowsAtIndexPaths:[NSArray arrayWithObjects:indexPath, nil] withRowAnimation:UITableViewRowAnimationNone];
            [self.tableView endUpdates];
        } failure:^(AFHTTPRequestOperation *operation, NSError *error) {
            NSLog(@"Error: %@", error);
            self.selectedFile.status = DOWNLOAD_ERROR;
        }];

        [operation setDownloadProgressBlock:^(NSUInteger bytesRead, long long totalBytesRead, long long totalBytesExpectedToRead) {
            dispatch_async(dispatch_get_main_queue(), ^{
                float percent = (float)totalBytesRead / (float)totalBytesExpectedToRead;
                NSLog(@"totalBytesExpectedToRead %d", (int)(percent * 100));
            });
        }];

        [operation start];

Upvotes: 2

Views: 1800

Answers (1)

sergio
sergio

Reputation: 69027

In a nutshell I would like to know how I can pass a object along with my AFHTTPRequestOperation so that on completion an operation can be performed on it. It could be as simple as an int or string and I can do an NSPredicate request based on this value.

This is where the power of closures/blocks comes into play. You do not need to pass anything special into a block. You can refer to any object which is lexically visible in the block and it will be available when the block is run.

From Apple docs (specifically, point 2):

Blocks represent typically small, self-contained pieces of code. As such, they’re particularly useful as a means of encapsulating units of work that may be executed concurrently, or over items in a collection, or as a callback when another operation has finished.

Blocks are a useful alternative to traditional callback functions for two main reasons:

  1. They allow you to write code at the point of invocation that is executed later in the context of the method implementation.
    Blocks are thus often parameters of framework methods.

  2. They allow access to local variables.
    Rather than using callbacks requiring a data structure that embodies all the contextual information you need to perform an operation, you simply access local variables directly.

If I understand correctly what you are trying to do, this should work:

TYPE_HERE* localSelectedFile = self.selectedFile;

    [operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
        NSLog(@"File downloaded to: %@", path);
        localSelectedFile.status = DOWNLOADED;
        localSelectedFile.is_stored = @YES;
        [self.tableView beginUpdates];
        [self.tableView reloadRowsAtIndexPaths:[NSArray arrayWithObjects:indexPath, nil] withRowAnimation:UITableViewRowAnimationNone];
        [self.tableView endUpdates];
       ...

where I am storing the value of self.selectedFile into a local variable at the moment operation is executed; then I am using that local variable into the block body. This is legal (this is the power of closures I was referring to: they keep with themselves information about their execution context) and will give you the reference to the object you have been processing.

You might find this interesting for an overall view.

Upvotes: 3

Related Questions