建议正确管理NSURLSession

时间:2014-03-05 05:26:03

标签: ios asynchronous download nsurlsession background-thread

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

我刚刚完成了在nsurlsession中更正我的数据下载的错误,但是我发现理解这些asynchronus请求有多困难,我认为我的解决方案并不是很好...... 下载错误也出现在2种不同的下载解决方案中,这让我觉得我忘了做某事......

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

我首先找到了project

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

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 / CTSessionOperation中的metod DownloadManager来更正此错误。我发表评论“dispatch_once”以使其有效,但我不认为这是一个很好的解决方案......

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

     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来避免此错误。

所以我们走了。我很困惑这2个解决方案。我不知道他们是否是管理下载的正确方法,我不认为我正确地纠正了错误,我一定错过了NSURLSession的逻辑。

你对改进这些解决方案有什么建议吗?或者你看到哪一个比另一个好得多?

2 个答案:

答案 0 :(得分: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解决方案我不会有很多代码要写下载一些数据

答案 1 :(得分: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与背景NSURLSession结合使用。请注意,如果应用程序终止,而下载将在后台完成,则此完成块将丢失(在执行后台会话时无效)。

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


  3. 以下是我的原始答案,其中我反对CTSessionOperation类,因为(a)将完成块与后台NSURLSession结合使用存在认知失调(因为这些块将是如果应用程序终止,则会丢失,即使下载将继续); (b)它使用非并发NSOperation进行异步任务,击败了使用基于NSOperation的实现的许多关键优势。虽然这两点肯定都有问题,但我认为这些问题仅次于我的观点,但我会将此保留在此作为参考。

    原始答案:

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

    尽管我对CTSessionOperation的结构性缺陷进行了批评,但是对dispatch_once进行评论以及创建独特的后台会话或创建标准前台会话的尝试补救措施肯定是而不是正确的方法,如果你需要后台下载。如果有的话,如果你试图解决CTSessionOperation在应用程序终止后没有处理后台会话的问题,那么实际上这是朝着错误的方向发展的,那就是。

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