使用 NSURLConnection 的异步下载被证明是不可靠的

Async downloads with NSURLConnection prove unreliable

我一直在使用 NSURLConnection 执行一些异步下载。基本上我从 Parse 后端检索一个 PFFile 并将其显示在 Web 视图中。我提供了一个下载到设备的选项。一切顺利,所有任务都按要求执行,但是,最近我注意到如果我以高速执行下载,其中一些不会显示在下载 VC 中。我不知道如何测试它,但我的理论是它会被切断,因为优先级设置为高,即使,用户在下载完成之前也不能做任何事情.你要退出当前的UIWebView到select另一个文件。这不是让我更难缩小范围的一致性行为。只有当我下载 > 退出 > 打开 > 并立即下载另一个文档 > 退出 > 重复直到完成并且没有特定的文档时才会发生。

问题:下载执行,我得到了创建文件的 NSLog,但是,当我转到下载时 VC 该文档未出现在 NSFRC 中,但仍显示为目录中的有效文件。

过程 下载是如何进行的,

UIWebView 加载从 Parse.com 查询的 PDF。这里没问题。 ProgressHUD 显示

一旦加载,progressHUD 就会消失。

如果他们想下载 PDF,只需单击 sheet 索引进行下载,下载过程就会开始。

另一个进度 HUD 显示,您可以在 NSURL didReceiveData 显示中看到。在下载完成之前,此 HUD 不允许用户交互。因此,在完全下载完成之前,您不能退出视图控制器以 select 另一个 pdf。所以 NO 我不会同时进行大量下载,而是一次下载一个。

然后用户可以在下载完成后退出,select 另一个加载相应 PDF 的 UITableViewCell 并重复该过程。

正在加载 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 {
            }

下载方法:

        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];
}

下载 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 它似乎下载了所有这些,并且 'creates' 一个文件,但是它并没有在 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]);

上面准确地记录了我下载的所有文件,但它们要么没有被识别为从 UIWebView 完全下载,要么创建文件路径在下载过程中以某种方式出错,因此它没有显示在下载视图控制器,但其他文档确实显示出来。它在每个会话中始终丢失 1-2 个,但就像我之前所说的,它从来没有丢失相同的文件,它是任何感觉丢失的东西。假设我下载 10 然后关闭应用程序然后重新打开它 9 只会出现在下载 VC 有时 8,但它记录为目录中的有效文件路径。

问题似乎是您的托管对象上下文线程限制,特别是您没有限制。你在原始线程上获取上下文,大概是主线程,但是你在后台块中捕获它并直接访问它。

您应该将保存文件的结果发送回主线程,然后在那里创建并保存新的托管对象。