【问题标题】:NSURLConnection timing outNSURLConnection 超时
【发布时间】:2012-06-06 04:26:17
【问题描述】:

我在 iPhone 应用程序中遇到 NSURLConnection 请求超时的间歇性问题。它似乎发生得越来越晚。一旦它进入这个状态,它就停留在那个状态。唯一的解决方案似乎是杀死应用程序并重新启动它。

观察:

  • 执行 NSURLConnection 的核心代码没有改变(最近添加的一些自定义用户代理代码除外)。
  • 尚未找到可重现的案例,但应用程序在后台运行一段时间后似乎会发生超时,尤其是在 3G(无 WiFi)上运行时。
  • 服务器上的 Apache 在遇到这些超时时没有记录来自客户端的请求。
  • 一些迹象表明邮件和 Safari 等其他应用受到影响(即出现超时),但并非始终如一。
  • 3G 覆盖在我所在的地方是稳定的,不排除触发问题的暂时性问题(假设不太可能)。
  • 所有请求都发送到我们自己的 API 服务器,并且是 POST 请求。
  • 由于 timeoutInterval 和 POST 请求的问题,我们使用自己的基于 NSTimer 的超时。我尝试过增加超时值——问题仍然存在。

其他杂项:

  • 应用最近已转换为 ARC。
  • 在 iOS 5.1.1 下运行应用程序。
  • 应用使用最新版本的 UrbanAirship、TestFlight 和 Flurry SDK。
  • 还使用 TouchXML 的 ARC 分支来解析响应。

如下所示,代码在主线程上运行。我假设该线程上出现了阻塞,但是我在挂起应用程序时看到的堆栈跟踪表明主线程很好。我认为 NSURLConnection 正在使用它自己的线程并且必须被阻止。

#define relnil(v) (v = nil)

- (id) initWebRequestController
{
    self = [super init];
    if (self)
    {
        //setup a queue to execute all web requests on synchronously
        dispatch_queue_t aQueue = dispatch_queue_create("com.myapp.webqueue", NULL);
        [self setWebQueue:aQueue];
    }
    return self;
}

- (void) getStuffFromServer
{
    dispatch_queue_t aQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_async(aQueue, ^{
        dispatch_sync([self webQueue], ^{            
            error_block_t errorBlock = ^(MyAppAPIStatusCode code, NSError * error){
                dispatch_async(dispatch_get_main_queue(), ^{
                    [[self delegate] webRequestController:self didEncounterErrorGettingPointsWithCode:code andOptionalError:error];
                });
            };

            parsing_block_t parsingBlock = ^(CXMLDocument * doc, error_block_t errorHandler){
                NSError * error = nil;

                CXMLNode * node = [doc nodeForXPath:@"apiResult/data/stuff" error:&error];
                if (error || !node) {
                    errorHandler(MyAppAPIStatusCodeFailedToParse, error);
                }
                else {
                    stuffString = [node stringValue];
                }

                if (stuffString) {
                    dispatch_async(dispatch_get_main_queue(), ^{
                        [[self delegate] webRequestController:self didFinishGettingStuff:stuffString];
                    });
                }
                else {
                    errorHandler(MyAppAPIStatusCodeFailedToParse, error);
                }
            };

            NSURL * url = [[NSURL alloc] initWithString:[NSString stringWithFormat:MyAppURLFormat_MyAppAPI, @"stuff/getStuff"]];

            NSMutableURLRequest * urlRequest = [[NSMutableURLRequest alloc] initWithURL:url];
            NSMutableDictionary * postDictionary = [NSMutableDictionary dictionaryWithObjectsAndKeys:
                                                    [[NSUserDefaults standardUserDefaults] objectForKey:MyAppKey_Token], @"token",
                                                    origin, @"from",
                                                    destination, @"to",
                                                    transitTypeString, @"mode",
                                                    time, @"time",
                                                    nil];

            NSString * postString = [WebRequestController httpBodyFromDictionary:postDictionary];
            [urlRequest setHTTPBody:[postString dataUsingEncoding:NSUTF8StringEncoding]];
            [urlRequest setHTTPMethod:@"POST"];

            if (urlRequest)
            {
                [self performAPIRequest:urlRequest withRequestParameters:postDictionary parsing:parsingBlock errorHandling:errorBlock timeout:kTimeout_Standard];
            }
            else
            {
                errorBlock(MyAppAPIStatusCodeInvalidRequest, nil);
            }

            relnil(url);
            relnil(urlRequest);
        });
    });
}

- (void) performAPIRequest: (NSMutableURLRequest *) request
     withRequestParameters: (NSMutableDictionary *) requestParameters
                   parsing: (parsing_block_t) parsingBlock 
             errorHandling: (error_block_t) errorBlock
                   timeout: (NSTimeInterval) timeout
{
    NSAssert([self apiConnection] == nil, @"Requesting before previous request has completed");

    NSString * postString = [WebRequestController httpBodyFromDictionary:requestParameters];
    [request setHTTPBody:[postString dataUsingEncoding:NSUTF8StringEncoding]];

    NSString * erVersion = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleShortVersionString"];
    NSString * erBuildVersion = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleVersion"];
    if ([erBuildVersion isEqualToString:erVersion] || [erBuildVersion isEqualToString:@""]) {
        erBuildVersion = @"";
    } else {
        erBuildVersion = [NSString stringWithFormat:@"(%@)", erBuildVersion];
    }
    NSString * iosVersion = [[UIDevice currentDevice] systemVersion];
    NSString * userAgent = [NSString stringWithFormat:@"MyApp/%@%@ iOS/%@", erVersion, erBuildVersion, iosVersion];
    [request setValue:userAgent forHTTPHeaderField:@"User-Agent"];

    [request setTimeoutInterval:(timeout-3.0f)];

    dispatch_sync(dispatch_get_main_queue(), ^{
        NSURLConnection * urlConnection = [[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:NO];

        if (urlConnection)
        {
            [self setApiConnection:urlConnection];

            requestParseBlock = [parsingBlock copy];
            requestErrorBlock = [errorBlock copy];

            NSMutableData * aMutableData = [[NSMutableData alloc] init];
            [self setReceivedData:aMutableData];
            relnil(aMutableData);

            [urlConnection scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];

            [urlConnection start];
            relnil(urlConnection);

            NSTimer * aTimer = [NSTimer scheduledTimerWithTimeInterval:timeout target:self selector:@selector(timeoutTimerFired:) userInfo:nil repeats:NO];
            [self setTimeoutTimer:aTimer];
        }
        else
        {
            errorBlock(MyAppAPIStatusCodeInvalidRequest, nil);
        }
    });

    //we want the web requests to appear synchronous from outside of this interface
    while ([self apiConnection] != nil)
    {
        [NSThread sleepForTimeInterval:.25];
    }
}

- (void) timeoutTimerFired: (NSTimer *) timer
{
    [[self apiConnection] cancel];

    relnil(apiConnection);
    relnil(receivedData);

    [self requestErrorBlock](MyAppAPIStatusCodeTimeout, nil);

    requestErrorBlock = nil;
    requestParseBlock = nil;
}


- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{    
    [self requestErrorBlock](MyAppAPIStatusCodeFailedToConnect, error);

    relnil(apiConnection);
    relnil(receivedData);
    [[self timeoutTimer] invalidate];
    relnil(timeoutTimer);
    requestErrorBlock = nil;
    requestParseBlock = nil;
}

- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
    [receivedData setLength:0];
}

- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
    [receivedData appendData:data];
}

- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{    
    MyAppAPIStatusCode status = MyAppAPIStatusCodeFailedToParse;

    CXMLDocument *doc = [[self receivedData] length] ? [[CXMLDocument alloc] initWithData:[self receivedData] options:0 error:nil] : nil;

    DLog(@"response:\n%@", doc);

    if (doc)
    {
        NSError * error = nil;
        CXMLNode * node = [doc nodeForXPath:@"apiResult/result" error:&error];
        if (!error && node)
        {
            status = [[node stringValue] intValue];

            if (status == MyAppAPIStatusCodeOK)
            {
                [self requestParseBlock](doc, [self requestErrorBlock]);
            }
            else if (status == MyAppAPIStatusCodeTokenMissingInvalidOrExpired)
            {
                [Definitions setToken:nil];

                [self requestMyAppTokenIfNotPresent];

                [Definitions logout];

                dispatch_async(dispatch_get_main_queue(), ^{
                    [[self delegate] webRequestControllerDidRecivedExpiredTokenError:self];
                });
            }
            else
            {
                [self requestErrorBlock](status, nil);                
            }
        }
        else
        {
            [self requestErrorBlock](status, nil);
        }
    }
    else
    {
        status = MyAppAPIStatusCodeUnexpectedResponse;
        [self requestErrorBlock](status, nil);
    }
    relnil(doc);

    relnil(apiConnection);
    relnil(receivedData);
    [[self timeoutTimer] invalidate];
    relnil(timeoutTimer);
    requestErrorBlock = nil;
    requestParseBlock = nil;
}

以下网址是应用程序处于问题状态时的队列/线程的一些屏幕截图。请注意,我相信线程 10 与前一次超时执行的取消有关,尽管互斥等待很奇怪。此外,线程 22 中有关 Flurry 的位在其他场合遇到问题时也不会始终出现。

堆栈跟踪截图:

http://img27.imageshack.us/img27/5614/screenshot20120529at236.png http://img198.imageshack.us/img198/5614/screenshot20120529at236.png

也许我忽略了这些痕迹中明显错误的地方,因为我对 iOS/Apple 开发还比较陌生。

如果我有 NSURLConnection 和相关代码的源代码,那么解决所有这些问题会简单得多,但就目前而言,我是在黑暗中摸索。

【问题讨论】:

  • 您发布的信息太多,但可能还不够。错误信息是什么?
  • 没有错误信息 - 就是这样。 NSURLConnection 启动,但从未完成,我们的 NSTimer 开始取消它。
  • 在想我到处寻找类似的问题之后,我偶然发现了这个:stackoverflow.com/questions/10149811/… 我想这可能只是我悲伤的原因。将尝试删除 TestFlight 并查看会发生什么。
  • 你滥用调度库而不知道效果。实际上,您的代码绑定了两个辅助线程,它们只是等待主线程上的工作完成。这没有任何意义。

标签: ios nsurlconnection


【解决方案1】:

删除 TestFlight 1.0 SDK 似乎可以解决问题。 TestFlight 还证实他们正在努力修复。鉴于该错误最初被其他人确认已经一个多月了,我想知道我们离修复还有多远。

【讨论】:

猜你喜欢
  • 2010-11-28
  • 2011-04-03
  • 1970-01-01
  • 2012-03-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多