Ios 正确管理会话的建议
我开始开发iphone应用程序,我需要一些关于NSURLSession的建议,以及关于如何正确管理数据下载和解析的建议 我刚刚完成在nsurlsession中为数据下载纠正一个错误,但是关于我发现理解这些异步请求有多困难,我认为我的解决方案不是很好。。。 另外,下载错误出现在两种不同的下载解决方案中,这让我觉得我忘记做什么了 在我的项目中,我下载了不同的xml文件(有时还有一些带有图片的zip文件),这些文件在显示它们的信息之前需要解析。这些信息可以很快改变,所以如果我再次加载我的页面,我想再次下载它们。 我在寻找一种简单的方法,以同样的方式管理所有下载,这样我就不必重新编写大量代码 我先找到的 因此,我只需使用该代码来管理下载:Ios 正确管理会话的建议,ios,asynchronous,download,nsurlsession,background-thread,Ios,Asynchronous,Download,Nsurlsession,Background Thread,我开始开发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];