soulshined
soulshined

Reputation: 10602

Async downloads with NSURLConnection prove unreliable

I have been using NSURLConnection to perform some async downloads. Basically I retrieve a PFFile from Parse backend and display it in a web view. I offer an option to download to device. Everything is good to go and all tasks perform as required, however, lately i've noticed if I perform downloads at a high rate of speed some of them don't show up in the Downloads VC. I don't know how to test it, but my theory is it's getting cut off because the priority is set to High, EVEN THOUGH, users can't do anything until the download is complete. You have to exit out of the current UIWebView to select another document. This isn't a consist behavior which makes it harder for me to narrow down. It only happens when I download > exit > open > & download another document right away > exit > repeat until done and there is no specific document it does it with.

THE PROBLEM : downloads perform, and I get an NSLog of file created, however, when I go to the Downloads VC the document doesn't show up in the NSFRC, but still shows as a valid file in the directory.

THE PROCESS How the download happens,

The UIWebView loads a PDF queried from Parse.com. No problem here. A ProgressHUD displays

Once loaded the progressHUD goes away.

If they want to download the PDF they just click on the action sheet index for downloading and it starts the download process.

Another Progress HUD displays as you can see in the NSURL didReceiveData displays. This HUD does not allow user interaction until the download is complete. So you can't exit the View Controller to select another pdf until it's done completely done downloading. So NO i am not conducting numerous downloads simultaneously, it's one download at a time.

Then the user can exit after download complete, select another UITableViewCell which loads the respective PDF and repeats the process.

Loading the PDF :

PFQuery *EGQuery = [PFQuery queryWithClassName:@"Classname"];
    [EGQuery whereKey:@"KeyName" equalTo:self.title];
    [EGQuery getFirstObjectInBackgroundWithBlock:^(PFObject *object, NSError *error) {
        if (!error) {
            if ([object objectForKey:@"EGFile"]) {
                PFFile *file = [object objectForKey:@"EGFile"];
                self.pdfURL = [file url];
                self.pdfName = [file name]; //Ends up as EG_2014_04 this is what I append to the filePath where it's stored so the filePath will be /PDFs/EG_2014_04
                [self.webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:self.pdfURL]]];
            } else {
            }

Download method :

        NSManagedObjectContext *context = [self managedObjectContext];
        NSError *error = nil;
        NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"Downloads"];
        [request setPredicate:[NSPredicate predicateWithFormat:@"pubnumber = %@", self.title]];
        [request setFetchLimit:1];
        NSUInteger count = [context countForFetchRequest:request error:&error];
        if (count == NSNotFound) {

        } else if (count == 0) {

            NSURL *url = [NSURL URLWithString:pdfURL]; //pdfURL is PFFile url
            NSURLRequest *request = [NSURLRequest requestWithURL:url];
            NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:request delegate:self];

            dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0ul);
            dispatch_async(queue, ^{
               self.backgroundTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
                    [connection start];
                    NSLog(@"Background time remaining = %.1f seconds", [UIApplication sharedApplication].backgroundTimeRemaining);
               }];
                NSData *pdfData = [[NSData alloc]
                                   initWithContentsOfURL:[NSURL URLWithString:self.pdfURL]];

                NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
                NSString *path = [[paths objectAtIndex:0] stringByAppendingPathComponent:@"PDFs"];
                NSString *filePath = [path stringByAppendingPathComponent:self.pdfName];

                NSFileManager *fm = [NSFileManager defaultManager];
                [fm createFileAtPath:filePath contents:pdfData attributes:nil];
                if ([fm createFileAtPath:filePath contents:pdfData attributes:nil])
                {
                    dispatch_async(dispatch_get_main_queue(), ^{
                    NSManagedObject *newDWNLD = [NSEntityDescription insertNewObjectForEntityForName:@"Downloads" inManagedObjectContext:context];
                    [newDWNLD setValue:self.title forKey:@"pubnumber"];
                    [newDWNLD setValue:self.pubTitle forKey:@"pubtitle"];
                    [newDWNLD setValue:self.pdfName forKey:@"puburl"]; // this is what I use for the file path name in the PDF directory and this is how I call it in my NSFRC
                    });
                    NSLog(@"File was created");
                } else {
                    NSLog(@"File not created");
                }
            });

- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
    expectedLength = MAX([response expectedContentLength], 1);
    currentLength = 0;
    HUD.dimBackground = YES;
    HUD.mode = MBProgressHUDModeAnnularDeterminate;
    HUD.labelText = @"Downloading...";
}

- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
currentLength += [data length];
HUD.progress = currentLength / (float)expectedLength;
}

- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
HUD.customView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"success.png"]];
HUD.mode = MBProgressHUDModeCustomView;
HUD.labelText = @"Success!";
HUD.detailsLabelText = @"Added to Downloads";
HUD.dimBackground = YES;
[HUD hide:YES afterDelay:1.6];
//Cancel Background task if completed
//[[UIApplication sharedApplication] endBackgroundTask:self.backgroundTask];
//self.backgroundTask = UIBackgroundTaskInvalid;
}

- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
NSLog(@"Error %@", error);
[HUD hide:YES];
}

DOWNLOADS VC

- (NSFetchedResultsController *)fetchedResultsController {
if (fetchedResultsController != nil) {
    return fetchedResultsController;
}

NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];

NSEntityDescription *entity = [NSEntityDescription entityForName:@"Downloads" inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];


[fetchRequest setFetchBatchSize:50];

NSSortDescriptor *azSort = [[NSSortDescriptor alloc] initWithKey:@"pubnumber" ascending:YES];
NSArray *azSortArray = @[azSort];

[fetchRequest setSortDescriptors:azSortArray];

NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:self.managedObjectContext sectionNameKeyPath:@"pubnumber" cacheName:nil];
aFetchedResultsController.delegate = self;
self.fetchedResultsController = aFetchedResultsController;

NSError *error = nil;
if (![self.fetchedResultsController performFetch:&error]) {
    NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
    abort();
}

return fetchedResultsController;
}

cellForRowAtIndexPath:

NSString *title = [NSString stringWithFormat:@"%@", [context valueForKey:@"pubnumber"]];
cell.textLabel.text = title;
etc etc

EDIT It seems to download all of them, and 'creates' a file, however it doesn't display all of them in the VC.

application DidFinishLaunchingWithOptions:

NSString *createPaths;
NSArray *documents = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
createPaths = [[documents objectAtIndex:0] stringByAppendingPathComponent:@"PDFs"];
 NSLog(@"PDFs directory: %@", [[NSFileManager defaultManager] contentsOfDirectoryAtPath:createPaths error:&error]);

The above accrucately logs all the files that I downloaded, but they just aren't either getting recognized as fully downloaded from the UIWebView or creating the file path is going awry somehow during the download process so it's not displaying in the downloads view controller but the other documents do show up. It's consistently 1-2 missing from each session but like i stated before, its never the same document missing, it's whatever feels like missing is missing. Say I download 10 then close the app then re open it 9 will only be there in the Downloads VC sometimes 8, but it logs as being a valid filePath in the directory.

Upvotes: 0

Views: 175

Answers (1)

Wain
Wain

Reputation: 119031

The problem appears to be your managed object context thread confinement, specifically you aren't confining. You get the context on the original thread, presumably the main thread, but then you capture it in the background block and access it directly.

You should take the result of saving your file and send it back to the main thread, then create and save the new managed object there.

Upvotes: 1

Related Questions