Ios 正确管理会话的建议

Ios 正确管理会话的建议,ios,asynchronous,download,nsurlsession,background-thread,Ios,Asynchronous,Download,Nsurlsession,Background Thread,我开始开发iphone应用程序,我需要一些关于NSURLSession的建议,以及关于如何正确管理数据下载和解析的建议 我刚刚完成在nsurlsession中为数据下载纠正一个错误,但是关于我发现理解这些异步请求有多困难,我认为我的解决方案不是很好。。。 另外,下载错误出现在两种不同的下载解决方案中,这让我觉得我忘记做什么了 在我的项目中,我下载了不同的xml文件(有时还有一些带有图片的zip文件),这些文件在显示它们的信息之前需要解析。这些信息可以很快改变,所以如果我再次加载我的页面,我想再次

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

我刚刚完成在nsurlsession中为数据下载纠正一个错误,但是关于我发现理解这些异步请求有多困难,我认为我的解决方案不是很好。。。 另外,下载错误出现在两种不同的下载解决方案中,这让我觉得我忘记做什么了

在我的项目中,我下载了不同的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];
这段代码在我第一次下载时就可以完美地工作。但是如果我尝试再次启动下载,下载就不会启动(所以没有错误,只是,这段代码下载一次就够了)

我通过修改
CTSessionOperation
/
DownloadManager
中的metod
(NSURLSession*)会话来纠正此错误。我在评论中加入了“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
,从而避免了这个错误

我们开始吧。我对这两种解决方案相当困惑。我不知道它们是否是管理下载的正确方法,我认为我没有正确地纠正错误,而且我肯定错过了
NSURLSession
的逻辑


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

一些观察结果:

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

    我建议:

    • 一次恢复
      dispatch\u
      逻辑(因为您的直觉是正确的;您肯定不想创建一堆会话,让旧会话继续运行);及

    • 返回此视图控制器时,在启动新请求之前取消所有挂起的请求

  • 您最初使用
    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
      与后台
      nsursession
      结合使用。请注意,如果应用程序被终止,而下载将在后台完成,则此完成块将丢失(不符合进行后台会话的目的)

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


  • 下面是我最初的回答,我在回答中抨击了
    CTSessionOperation
    类,因为(a)将完成块与后台
    nsursession
    结合使用时存在认知失调(因为如果应用程序终止,即使下载将继续,这些块也将丢失);(b)它对异步任务使用了非并发的
    NSOperation
    ,破坏了使用基于
    NSOperation
    的实现的许多关键好处。虽然这两个观点都存在问题,但我认为这些问题是我上面观点的次要问题,但我将在这里保留这一点以供参考

    原始答复:

    您所说的“第一次下载时就可以完美地工作;但如果我再次尝试启动,则[不会]”是什么意思?你是说应用程序被终止,下载仍在后台进行吗?原始的
    CTSessionOperation
    似乎无法正确处理,因为它试图使用
    NSOperation
    和完成块的标准技巧,但所有这些都与即使在应用程序终止后仍继续进行的
    NSURLSession
    后台会话不兼容。一旦应用程序被终止,NSURLSession
    后台请求将继续进行,但所有操作和完成块都将完全丢失。我认为这个
    CTSessionOperation
    没有抓住要点。你真的无法享受丰富的后台会话,即使在应用程序终止后仍能继续进行,并且希望保留你第一次启动后台下载任务时创建的操作和完成块。您必须坚持使用
    NSURLSessionDownloadTask
    对象,并且只使用委托方法;没有完成块或操作

    尽管我对
    CTSessionOperation
    的结构缺陷提出了批评,但我还是试图通过注释<
        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];