为什么我有时会收到并发 NSURLSession 请求的混乱回复

Why do I sometimes get mangled replies with concurrent NSURLSession requests

我正在开发一个 OS X (Yosemite) 应用程序,它使用 NSURLSession [=33] 从互联网上异步下载两种类型的 csv 数据(称之为 A 类和 B 类) =] 。每种类型的 csv 都有多个请求。每个请求都是它自己专用的 session 包裹在自定义 class 中。有一个基本请求 class,每种类型都有一个子 class。 (事后看来,这可能不是一个理想的设计,但我认为与我的问题无关)。

该应用程序的构建使得每种类型的 csv 数据按顺序下载 queue。每种类型一次只能有一个请求处于活动状态,但两种类型可以同时发生,并且都使用主线程进行委托回调。所有这些通常都工作正常。

我遇到的问题是,有时在交通繁忙的情况下,我会收到 "cross hearing",即有时我会收到对 B 类请求的响应,该请求被报告为已成功完成,但其中包含许多 B 类请求cvs 行,然后在后面标记了一些 A 类行 - 所以我有时(很少)在我的 B 类请求中获取 A 类数据。 (或相反)。

基本上它看起来像 Apples API 中的 "switching" 逻辑混淆了哪个传入数据包属于哪个 request/session。两种不同的请求类型转到不同的 URL,但它们是相关的,可能它们最终都解析到同一个 IP,我不确定。我想知道是否有与数据包 headers 有关的东西,如果它们来自同一台服务器,这使得很难确定它们属于哪个请求(我对互联网协议不够了解,不知道这是不是一个明智的猜测)。如果是这种情况,那么解决方案必须是确保所有请求都在一个 queue 中,这样它们就不能同时处于活动状态,但在我确信没有其他解决方法之前,我不想进行大型架构更改.

我查找了类似的问题并发现了这个旧问题 (Why is my data getting corrupted when I send requests asynchronously in objective c for iOS?),它似乎描述了完全相同的问题,但不幸的是它没有答案。除此之外,我没有发现任何类似的东西,所以我想我在这里做了一些愚蠢的事情,但是在我开始更改架构以修复它之前知道为什么会出现这个问题会很好。

有没有人以前见过这种情况并且知道原因和解决方法?

我没有包含任何代码,因为我觉得没有意义,因为它似乎是一个体系结构问题,如果我添加代码,它需要很多。但是,如果有助于理解问题,我将很乐意添加您的建议。

编辑:

下面添加了相关的(我希望)代码。注意 objects 仅为一次拍摄。请求的参数由 init 方法注入,NSURLSession 仅用于单个任务。因此 session 在启动后无效,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;
 }

首先:您不应该为每个请求创建一个新会话。这不再是会话。来自 docs:

With the NSURLSession API, your app creates one or more sessions, each of which coordinates a group of related data transfer tasks. For example, if you are writing a web browser, your app might create one session per tab or window, or one session for interactive use and another session for background downloads. Within each session, your app adds a series of tasks, each of which represents a request for a specific URL (following HTTP redirects if necessary).

其次:你将会话等存储在哪里,所以它没有被释放?

您的主要问题:显然您开始新的请求,而请求可能 运行。但是您只有一个 NSMutableData 实例接收 -URLSession:task:didReceiveData: 中的数据:许多请求,一个存储……当然会混淆。

我终于找到了我的(愚蠢的)错误。为了将来参考,该问题是由于未能意识到返回的数据不是零终止的。

在我的案例中请求的大部分数据是 XML 并且 NSXMLParserclass 想要 NSData 没有额外的尾随零,这样效果很好。

但是偶尔失败的请求使用 CSV 格式,其中数据通过由 [NSString stringWithUTF8String] 创建的 NSString 传递,它期望以零终止的 c 样式字符串作为输入。这是罪魁祸首。通常它会正常工作。有时它会彻底失败,有时它只是缓冲区溢出并获得一些先前在同一内存区域中的请求数据。这些是我在发布问题时注意到的情况。

因此解决方案是切换到使用 [[NSString alloc] initWithData: encoding:NSUTF8StringEncoding],它适用于非 null-terminated NSData 缓冲区。