为什么我有时会收到带有并发 NSURL 请求的损坏回复



我正在开发一个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]

最新更新