2012-05-31 40 views
5

我一直有NSURLConnection请求在我们的iPhone应用程序超时的间歇性问题。这似乎是最近发生的事情。一旦进入这种状态,它就会保持这种状态。唯一的解决方案似乎是杀死应用程序并重新启动它。NSURLConnection超时

观察:

  • 执行该NSURLConnection的核心代码没有改变(除了最近添加一些自定义用户代理代码)。
  • 还没有找到可重复使用的情况,但超时后似乎应用程序已经坐在后台一段时间,特别是如果运行在3G(没有WiFi)。
  • 服务器上的Apache在遇到这些超时时没有记录来自客户端的请求。
  • 一些迹象表明其他应用程序(如Mail和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; 
} 

下面的URL是应用程序处于有问题状态时队列/线程的一些屏幕截图。请注意,我相信线程10与在上一次超时时执行的取消有关,尽管互斥等待很好奇。此外,线程22中关于Flurry的位在其他场合遇到问题时并不一致。

堆栈跟踪截图:

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

也许我忽视的东西显然是错误的那些痕迹,因为我是比较新的的iOS /苹果的发展。

所有这一切都将是非常简单的解决,如果我有NSURLConnection的和相关代码的源代码,但如它,我在这一点上采取刺在黑暗中。

+0

您发布的信息太多,但可能还不够。什么是错误信息? – Mundi

+0

没有错误信息 - 就是这样。 NSURLConnection开始,但从未完成,我们的NSTimer开始取消它。 – mackinra

+0

想到我到处搜索类似的问题后,我只是无意中发现了这个问题:http://stackoverflow.com/questions/10149811/why-does-nsurlconnection-fail-to-reach-the-backend我认为它可能只是我悲痛的原因。将尝试撕掉TestFlight,看看会发生什么。 – mackinra

回答

4

卸下TestFlight 1.0 SDK似乎来解决这个问题。TestFlight也证实他们正在进行修复。鉴于该错误最初由其他人确认已经过去了一个月,我想知道我们有多接近修复。

+0

我想我在堆栈跟踪中使用互斥体的东西...线程10中的TFRunLoopOperation是TestFlight代码。有关此TestFlight错误的进一步讨论如下:https://github.com/AFNetworking/AFNetworking/issues/307 – mackinra

相关问题