我正在开发一个OS X(优胜美地(应用程序,该应用程序使用NSURLSession API从互联网异步下载两种类型的csv数据(称为A型和B型(。每种类型的 csv 都有多个请求。每个请求都是它自己的专用会话,包装在自定义类中。有一个基请求类,每种类型都有一个子类。(事后看来,可能不是一个理想的设计,但我认为与我的问题无关(。
该应用程序的构造使得每种类型的 csv 数据都按顺序队列下载。每种类型的请求一次只能处于活动状态,但两种类型可以同时发生,并且都使用主线程进行委托回调。所有这些通常都很好。
我看到的问题是,有时在交通繁忙的情况下,我会得到"交叉听力",即我有时会收到对 B 型请求的响应,该请求报告为成功完成,但它包含许多 B 型 cvs 行,然后是一些标记在之后的 A 型行 - 所以我有时(很少(在我的 B 型请求中获取 A 型数据。(或相反(。
基本上,看起来Apples API中的"交换"逻辑对哪个传入数据包属于哪个请求/会话感到困惑。两种不同的请求类型转到不同的 URL,但它们是相关的,它们最终可能都解析为同一个 IP,我不确定。我想知道如果数据包标头来自同一服务器,是否可能与数据包标头相关,这使得很难确定它们属于哪个请求(我对互联网协议不够好,无法知道这是否是一个明智的猜测(。如果是这种情况,那么解决方案必须是确保所有请求都在一个队列中,以便它们不能同时处于活动状态,但在我相信没有其他解决方法之前,我不想进行大型架构更改。
我寻找了类似的问题并找到了这个老问题(为什么当我在 iOS 的目标 c 中异步发送请求时,我的数据会损坏? 这似乎描述了完全相同的问题,但不幸的是它没有答案。除此之外,我没有发现任何类似的东西,所以我想我在这里做了一些愚蠢的事情,但在我开始更改架构以修复它之前,最好知道为什么会出现这个问题。
以前有没有人见过这个,知道原因和解决方法是什么?
我没有包含任何代码,因为我觉得没有意义,因为它似乎是一个架构问题,如果我添加代码,则需要很多。但是,如果这有助于理解问题,我很乐意添加您的任何建议。
编辑:
下面添加了相关的(我希望(代码。注意对象只能是一次拍摄。请求的参数由 init 方法注入,NSURLSession 仅用于单个任务。因此,会话在启动后失效,NSMutableData 阵列在解析数据后释放。
-(BOOL)executeRequest {
NSURLSessionConfiguration *theConfig = [NSURLSessionConfiguration ephemeralSessionConfiguration];
NSURLSession *theSession = [NSURLSession sessionWithConfiguration:theConfig delegate:self delegateQueue:[NSOperationQueue mainQueue]];
NSURLRequest *theRequest = [NSURLRequest requestWithURL:self.queryURL cachePolicy: NSURLRequestReloadIgnoringLocalCacheData timeoutInterval:BSTTIMEOUT];
NSURLSessionDataTask *theTask = [theSession dataTaskWithRequest:theRequest];
if(!theTask) {
return NO;
}
[theTask resume];
[theSession finishTasksAndInvalidate];
self.internetData = [NSMutableData dataWithCapacity:0];
return YES;
}
-(void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data {
[self.internetData appendData:data];
return;
}
-(void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
if((error)||(![self parseData]))
{
self.internetData = nil;
if(!error) {
NSDictionary *errorDictionary = @{ NSLocalizedDescriptionKey : @"Parsing of internet data failed", NSLocalizedFailureReasonErrorKey : @"Bad data was found in received buffer"};
error = [NSError errorWithDomain:NSCocoaErrorDomain code:EIO userInfo:errorDictionary];
}
NSDictionary* ui = [NSDictionary dictionaryWithObject:error forKey:@"Error"];
[[NSNotificationCenter defaultCenter] postNotificationName:[self failNotification] object:self userInfo:ui];
return;
}
[[NSNotificationCenter defaultCenter] postNotificationName:[self successNotification] object:self];
return;
}
首先:您不应该为每个请求创建一个新会话。这不再是会话。从文档中:
使用 NSURLSession API,应用可以创建一个或多个会话,每个会话协调一组相关的数据传输任务。例如,如果您正在编写 Web 浏览器,则您的应用可能会为每个选项卡或窗口创建一个会话,或者创建一个用于交互使用的会话,另一个用于后台下载的会话。在每个会话中,您的应用都会添加一系列任务,每个任务表示对特定 URL 的请求(如有必要,遵循 HTTP 重定向(。
第二:你在哪里存储会话等,所以它没有被解除分配?
您的主要问题:显然,您在请求可能正在运行时启动新请求。但是您只有一个NSMutableData
实例可以接收-URLSession:task:didReceiveData:
中的数据:许多请求,一个存储...当然,这混为一谈。
我终于设法追踪到我的(愚蠢的(错误。供将来参考,该问题是由于未能意识到返回的数据未终止零引起的。
在我的情况下,请求的大部分数据都是XML,NSXMLParser
类想要一个没有额外尾随零的NSData
,以便运行良好。
但是偶尔失败的请求使用 CSV 格式,其中数据通过[NSString stringWithUTF8String]
创建的NSString
传递,该需要以零终止的 c 样式字符串作为输入。这是罪魁祸首。通常它按预期工作。有时它完全失败,有时它只是做了缓冲区溢出,并获得了同一内存区域中的一些先前请求数据。这些是我在发布问题时注意到的情况。
因此,解决方案是切换到使用与非 null 终止的 NSData
缓冲区一起使用的 [[NSString alloc] initWithData: encoding:NSUTF8StringEncoding]
。