1

我开始开发一个 iphone 应用程序,我需要一些关于 NSURLSession 以及如何正确管理我的数据的下载和解析的建议。

我刚刚纠正了在 nsurlsession 中下载我的数据的错误,但是关于我很难理解这些异步请求,我认为我的解决方案不是很好......下载错误也出现了 2 个不同的解决方案下载,这让我觉得我忘了做某事......

在我的项目中,我下载了不同的 xml 文件(有时还有一些带有图片的 zip 文件),我需要先解析这些文件以显示它们的信息。这些信息可以快速更改,所以如果我再次加载我的页面,我想再次下载它们。我一直在寻找一种以相同方式管理所有下载的简单方法,这样我就不必重新编写大量代码。

我首先找到了这个项目

有了这个,我只需要使用该代码来管理下载:

NSString *downloadUrl = @"https://www.url.com";

        NSURL *location = [NSURL URLWithString:downloadUrl];
        // DownloadManager  is my version of the CTSessionOperation of the github project
        DownloadManager *operation = [DownloadManager new];
        operation.downloadUrl = downloadUrl;
        operation.completionAction = ^(NSURL *xmlUrl, BOOL success){
            dispatch_async(dispatch_get_main_queue(), ^{
                if (success){
                    regions = [[TeamChoiceManager sharedManager] parseRegions:[NSData dataWithContentsOfURL:location]];
                    [self.tableView performSelectorOnMainThread:@selector(reloadData) withObject:Nil waitUntilDone:YES];
                }
            });
        };
        operation.isBackground = YES;

       [operation enqueueOperation];

这段代码在我第一次下载时完美运行。但是如果我尝试再次启动下载,则不会启动下载(所以没有错误,只是,此代码下载一次,仅此而已)。

我通过修改/(NSURLSession *)session中的方法来纠正这个错误。我在评论中添加了“dispatch_once”以使其正常工作,但我认为这不是一个好的解决方案......CTSessionOperationDownloadManager

我尝试了另一种导致相同错误的解决方案。我使用以下代码管理下载:

     NSString *regionsUrl= @"url";

            NSURLSessionConfiguration *sessionConfig =
            [NSURLSessionConfiguration defaultSessionConfiguration];
// My solution to the bug          
/*NSURLSessionConfiguration *backgroundConfiguration = [NSURLSessionConfiguration
                                                                  backgroundSessionConfiguration:[NSString stringWithFormat:@"com.captech.mysupersession.BackgroundSession%d",numBGSession]]; */
            //   numBGSession++; this is a static NSInteger


NSURLSession *session =
        [NSURLSession sessionWithConfiguration:backgroundConfiguration
                                      delegate:teamChoiceDetailViewController
                                 delegateQueue:nil];

        NSURLSessionDownloadTask *sessDLTask =
        [session downloadTaskWithURL:[NSURL URLWithString:regionsUrl]];

        [sessDLTask resume];

在委托中:

-(void)URLSession:(NSURLSession *)session
     downloadTask:(NSURLSessionDownloadTask *)downloadTask
didFinishDownloadingToURL:(NSURL *)location
{
    dispatch_async(dispatch_get_main_queue(), ^{
        self.regions = [[TeamChoiceManager sharedManager] parseRegions:[NSData dataWithContentsOfURL:location]];
        [self.tableView performSelectorOnMainThread:@selector(reloadData) withObject:Nil waitUntilDone:YES];
    });
}

NSURLSessionConfiguration使用此解决方案,我每次尝试下载时都通过创建自定义来避免错误。

所以我们开始吧。我对这两种解决方案感到很困惑。我不知道它们是否是管理下载的正确方法,我认为我没有正确纠正错误,而且我一定错过了NSURLSession.

您对改进这些解决方案有什么建议,或者您认为其中一个比另一个好得多?

4

2 回答 2

1

编辑:如果有人正在寻找一种更通用的解决方案来轻松正确地管理网络事务,您可以查看AFNetworking,它有一些魔力(另外,您可以找到很多教程)。

我放弃了开发适用于每种情况的东西(特别是如果它是后台会话,因为我不需要它)。最后,我刚刚创建了一个带有静态会话的类,它管理下载的代表,以及我在 didFinishDownloadingToURL 中使用的一个块来管理下载的数据。当然不是完美的,但目前已经足够好了。

    typedef void (^CTCompletionBlock)(NSURL *location, NSError* err);

    @interface DownloadManager :  NSObject <NSURLSessionDelegate, NSURLSessionTaskDelegate, NSURLSessionDownloadDelegate>

    @property (nonatomic, retain) NSURLSessionDownloadTask *dlTask;
    @property (nonatomic, retain) NSString *location;
    @property (strong) CTCompletionBlock afterDLBlock;

    + (DownloadManager *)sharedManager;
    -(void)downloadTask;
    @end


    //
    //  DownloadManager.m
    //  MVCTest
    //
    //  Created by 
    //

    #import "DownloadManager.h"
    #import "AppDelegate.h"


    static DownloadManager *instance = nil;
    static NSURLSession *session = nil;

    @implementation DownloadManager

    + (DownloadManager *)sharedManager {
        if (instance == nil) {
            //session = [DownloadManager sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:self delegateQueue:nil];
            instance = [DownloadManager new];
        }
        return instance;
    }

    + (id)new
    {
        return [[self alloc] init];
    }
    - (id)init{
        self = [super init];
        session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]
        return self;
    }

    -(void)downloadTask{
        self.dlTask = [session downloadTaskWithURL:[NSURL URLWithString:self.location]];
        [self.dlTask resume];
    }

    #pragma mark - NSURLSessionDownloadDelegate
    - (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location {

 NSError *error;
        if (self.afterDLBlock){
            self.afterDLBlock(location, error);
        }

    }

//i still have to manage the delegate...
    - (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite {}

    - (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didResumeAtOffset:(int64_t)fileOffset expectedTotalBytes:(int64_t)expectedTotalBytes {}

    #pragma mark - NSURLSessionTaskDelegate

    - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {}

    -(void)URLSession:(NSURLSession *)session didBecomeInvalidWithError:(NSError *)error{}

    #pragma mark - NSURLSessionDelegate

    - (void)URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session {}

    @end

有了这个类,我只需要编写这段代码来管理下载的数据:

typeof(self) __weak weakSelf = self;
NSString *downloadUrl = @"http://www.whatyouwant.com";
[DownloadManager sharedManager].location = downloadUrl;
[DownloadManager sharedManager].afterDLBlock = ^(NSURL *location, NSError *error) {
        weakSelf.regions = [[TeamChoiceManager sharedManager] parseRegions:[NSData dataWithContentsOfURL:location]];

        dispatch_sync(dispatch_get_main_queue(), ^{
            [weakSelf.activityViewIndicator stopAnimating];
            [weakSelf.tableView reloadData];
        });
};
[[DownloadManager sharedManager] downloadTask];

我仍然需要管理错误和委托,但是使用 taht 解决方案我将不需要编写大量代码来下载一些数据

于 2014-03-13T07:04:58.303 回答
0

几点观察:

  1. 如果您使用背景NSURLSession,请注意这可能比标准慢NSURLSession。而且,如果您进行大量下载,您的会话可能会因之前的一些请求而积压,因此当您返回时,它似乎什么也没做(实际上,它可能只是忙于尝试完成尝试更新不存在或不再可见的表视图实例的先前任务请求)。这可以解释为什么创建新会话似乎可以正常工作(因为新会话不会将新任务排在先前排队的任务之后,而是并行运行它们)。

    我会建议:

    • 恢复dispatch_once逻辑(因为你的直觉是正确的;你绝对不想做一堆会话,让旧的会话在那里运行);和

    • 当您返回此视图控制器时,请在启动新请求之前取消任何待处理的请求。

  2. 您最初的尝试使用completionAction是有问题的,因为:

    • 您正在保留视图控制器。所以而不是:

      operation.completionAction = ^(NSURL *xmlUrl, BOOL success){
          dispatch_async(dispatch_get_main_queue(), ^{
              if (success){
                  regions = [[TeamChoiceManager sharedManager] parseRegions:[NSData dataWithContentsOfURL:location]];
                  [self.tableView performSelectorOnMainThread:@selector(reloadData) withObject:Nil waitUntilDone:YES];
              }
          });
      };
      

      你可能想要:

      typeof(self) __weak weakSelf = self;
      operation.completionAction = ^(NSURL *xmlUrl, BOOL success){
          dispatch_async(dispatch_get_main_queue(), ^{
              if (success){
                  regions = [[TeamChoiceManager sharedManager] parseRegions:[NSData dataWithContentsOfURL:location]];
                  [weakSelf.tableView reloadData]; // don't need that `performSelectorOnMainThread` call
              }
          });
      };
      
    • 您还尝试将它completionAction与 background 结合使用NSURLSession。请注意,如果应用程序终止,而下载将在后台完成,则此完成块将丢失(破坏了执行后台会话的目的)。

      如果您确实需要使用后台会话,则应将completionAction其中的任何逻辑移至下载委托方法本身。您不能将它completionAction与后台会话结合使用并期望它在应用程序终止后仍然存在(即使NSURLSessionDownloadTask对象会)。


以下是我的原始答案,我在其中猛烈抨击了CTSessionOperation课堂,因为 (a) 在将完成块与背景结合使用时存在认知失调NSURLSession(因为如果应用程序终止,这些块将丢失,即使下载将继续); NSOperation(b) 它对异步任务使用非并发,破坏了使用NSOperation-based 实现的许多关键好处。虽然这两点肯定是有问题的,但我认为这些担忧是我上述观点的次要问题,但我将把它留在这里以供参考。

原答案:

“我第一次下载时可以完美运行;但如果我再次尝试启动则[不]”是什么意思?您是在谈论应用程序已终止并且仍在后台进行下载吗?原始CTSessionOperation版本似乎无法正确处理该问题,因为它试图使用标准技巧和完成块,但所有这些都与即使在应用程序终止后仍继续进行的后台会话NSOperation不兼容。NSURLSession一旦应用程序终止,NSURLSession后台请求将继续进行,但所有操作和完成块将完全丢失。我认为这CTSessionOperation在这里错过了标记。即使在应用程序终止后,您仍然无法享受后台会话的丰富性,并期望保留您在首次启动后台下载任务时创建的操作和完成块。您必须坚持使用NSURLSessionDownloadTask对象并仅使用委托方法;没有完成块或操作。

尽管我批评了 似乎是结构性缺陷,但如果您需要后台下载CTSessionOperation,尝试注释掉dispatch_once并创建独特的后台会话或创建标准的前台会话绝对不是正确的方法。如果有的话,如果你试图解决CTSessionOperation应用程序终止后无法处理后台会话的问题,我认为这实际上是在错误的方向。

所以,问题是您是否真的想享受后台的全部功能NSURLSession(在这种情况下,您必须放弃完成块和操作模式CTSessionOperation),或者您是否可以接受非后台NSURLSession下载时终止下载应用程序本身会终止,并且您只希望能够在用户再次启动应用程序时从头开始重新启动它们。

于 2014-03-05T08:00:09.840 回答