2012-05-31 26 views
5

He estado teniendo problemas intermitentes con el tiempo de espera de las solicitudes NSURLConnection en nuestra aplicación de iPhone. Parece estar ocurriendo más tarde. Una vez que entra en este estado, permanece en ese estado. La única resolución parece ser matar la aplicación y reiniciarla.NSURLConnection tiempo de espera

Observaciones:

  • El código del núcleo que ejecuta el NSURLConnection no ha cambiado (excepto por algún código de usuario-agente personalizado añadido recientemente).
  • Todavía no hemos encontrado una funda reproducible, pero los tiempos de espera parecen ocurrir después de que la aplicación ha estado en segundo plano por un tiempo, especialmente si se ejecuta en 3G (sin WiFi).
  • Apache en el servidor no está registrando solicitudes del cliente mientras está experimentando estos tiempos de espera.
  • Algunas indicaciones de que otras aplicaciones, como Mail y Safari se ven afectadas (es decir, que experimentan tiempos de espera), aunque no siempre.
  • La cobertura 3G es sólida donde estoy, no para descartar un problema transitorio que desencadene el problema (se supone que no es probable).
  • Todas las solicitudes van a nuestro propio servidor de API, y son solicitudes POST de descanso.
  • Utilizamos nuestro propio tiempo de espera basado en NSTimer, debido a los problemas con timeoutInterval y solicitudes POST. He intentado jugar con aumentar el valor de tiempo de espera, el problema todavía ocurre.

Otras cosas Varios:

  • App fue recientemente convertido al ARC.
  • Ejecutando la aplicación en iOS 5.1.1.
  • La aplicación utiliza las últimas versiones de UrbanAir, TestFlight y Flurry SDK.
  • También utilizando la rama ARC de TouchXML para analizar las respuestas.

Como puede ver a continuación, el código se ejecuta en el hilo principal. Supuse que algo está bloqueando en ese hilo, pero los rastros de pila que veo al suspender la aplicación sugieren que el hilo principal está bien. Supongo que NSURLConnection está usando su propio hilo y debe ser bloqueado.

#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; 
} 

Las siguientes URL son algunas capturas de pantalla de las colas/subprocesos cuando la aplicación se encontraba en el estado problemático. Tenga en cuenta, creo que el hilo 10 está relacionado con la cancelación realizada en el tiempo de espera anterior, aunque la espera mutex es curiosa. Además, el bit en el hilo 22 sobre Flurry no aparece constantemente cuando se experimenta el problema en otras ocasiones.

imágenes seguimiento de la pila:

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

Tal vez estoy pasando por alto algo obviamente erróneo en esas trazas, como estoy relativamente nuevo en el desarrollo de iOS/Apple.

Todo esto sería mucho más simple de resolver si tuviera la fuente de NSURLConnection y el código relacionado, pero tal como está, en este momento estoy recibiendo ataques en la oscuridad.

+0

Está publicando demasiada información, pero quizás no la suficiente. ¿Cuál es el mensaje de error? – Mundi

+0

No hay mensaje de error: esa es la cuestión. La NSURLConnection se inicia, pero nunca se completa, y nuestro NSTimer se activa para cancelarla. – mackinra

+0

Después de pensar que busqué en todas partes un problema similar, me encontré con esto: http://stackoverflow.com/questions/10149811/why-does-nsurlconnection-fail-to-reach-the-backend Creo que podría ser solo la causa de mi dolor Intentará eliminar TestFlight y ver qué sucede. – mackinra

Respuesta

4

La eliminación de TestFlight 1.0 SDK pareció solucionar el problema.TestFlight también confirmó que están trabajando en una solución. Dado que ha pasado más de un mes desde que el error fue confirmado originalmente por otros, me pregunto qué tan cerca estamos de conseguir una solución.

+0

Supongo que toqué algo con el mutex en los rastros de la pila ... que TFRunLoopOperation en el hilo 10 es el código TestFlight. Puede encontrar más información sobre este error de TestFlight aquí: https://github.com/AFNetworking/AFNetworking/issues/307 – mackinra

Cuestiones relacionadas