我正在开发一个OS X(Yosemite)应用程序,该应用程序使用NSURLSession API从Internet异步下载两种类型的csv数据(称为类型A和类型B)。每种类型的csv都有多个请求。每个请求都是自己的专用会话,包含在自定义类中。有一个基本请求类,每个类型都有一个子类。 (事后看来,这可能不是一个理想的设计,但我认为与我的问题无关)。
构建应用程序,以便在顺序队列中下载每种类型的csv数据。每种类型只有一个请求可以同时处于活动状态,但这两种类型可以同时发生,并且都使用主线程进行委托回调。所有这些通常都很好。
我看到的问题是,有时流量很大,我得到“交叉听证”,即我有时得到一个回复到B类型请求的响应,该请求报告为已成功完成,但它包含多个B型cvs行和之后标记了一些A类线 - 所以我有时(很少)在我的B类请求中获得A类数据。 (或者相反)。
基本上,Apples API中的“切换”逻辑看起来很混淆哪个传入数据包属于哪个请求/会话。两种不同的请求类型会转到不同的URL,但它们是相关的,可能是它们最终都解析为相同的IP,我不确定。我想知道是否可能存在与数据包标题相关的东西,如果它们来自同一服务器,这使得很难确定它们属于哪个请求(我对互联网协议不够好,不知道这是否是一个合理的猜测) 。如果是这种情况,则解决方案必须是确保所有请求都在一个队列中,以便它们不能同时处于活动状态,但我不想在我确信没有其他解决方法之前进行大型架构更改。
我找了类似的问题,发现这个老问题(Why is my data getting corrupted when I send requests asynchronously in objective c for iOS?)似乎描述了完全相同的问题,但遗憾的是它没有答案。除此之外我没有发现任何类似的东西,所以我想我在这里做了一些愚蠢的事情但是在我开始改变架构来修复它之前知道为什么会出现这个问题会很好。
有没有人见过这个,知道原因和解决方法是什么?
我没有包含任何代码,因为我觉得没有任何意义,因为它似乎是一个架构问题,如果我添加了代码,它将需要很多。但是,如果有助于理解这个问题,我会很乐意添加您的建议。
编辑:
下面添加的相关(我希望)代码。注意对象仅限一次。请求的参数由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;
}
答案 0 :(得分:0)
首先:您不应为每个请求创建新会话。这不再是会话了。来自docs:
使用NSURLSession API,您的应用会创建一个或多个会话,每个会话都会协调一组相关的数据传输任务。例如,如果您正在编写Web浏览器,则您的应用程序可能会为每个选项卡或窗口创建一个会话,或者一个会话用于交互式使用,另一个会话用于后台下载。在每个会话中,您的应用程序会添加一系列任务,每个任务都代表对特定URL的请求(如果需要,可以在HTTP重定向之后)。
第二:你在哪里存储会话等,所以它没有被解除分配?
您的主要问题:显然您在请求可能正在运行时启动新请求。但是,您只有一个NSMutableData
实例接收-URLSession:task:didReceiveData:
中的数据:许多请求,一个存储......当然会混淆。
答案 1 :(得分:0)
我终于设法追查我的(愚蠢的)错误。为了将来参考,问题是由于未能意识到返回的数据不是零终止。
在我的案例中请求的大多数数据都是XML,而NSXMLParser
类想要NSData
而没有额外的尾随零,因此效果很好。
但偶尔失败的请求使用CSV格式,其中数据通过由NSString
创建的[NSString stringWithUTF8String]
,该[[NSString alloc] initWithData: encoding:NSUTF8StringEncoding]
期望零终止的c样式字符串作为输入。这是罪魁祸首。它经常起作用。有时它完全失败了,有时它只是进行了缓冲区溢出,并得到了一些以前在同一内存区域的请求数据。这些是我在发布问题时注意到的案例。
因此解决方案是切换到NSData
的使用,它使用非空终止的$arr = array('cat'=>'cool', 2=>array('something'=>"<script>alert('hello');</script> hello!"));
print_r($arr); // something bad pops up
缓冲区。