2012-05-31 7 views
5

iPhoneアプリでNSURLConnectionリクエストがタイムアウトする問題が断続的に発生しています。遅くなっているようです。この状態になると、その状態にとどまります。唯一の解決策は、アプリを殺して再起動するようだ。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は、以前のタイムアウトで実行されたキャンセルに関連していると思いますが、ミューテックスの待ち時間は不思議です。また、他の機会に問題が発生した場合、Flurryについてのスレッド22のビットが一貫して表示されません。

スタックトレーススクリーンショット:

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はまた、彼らが修正に取り組んでいることを確認した。バグが最初に他の人によって確認されてから1ヶ月以上が経過していることを考えれば、私たちは修正を得るためにどれほど近づいているのだろうか。

+0

スタックトレースのミューテックスで何かをヒットしたと思います...スレッド10のTFRunLoopOperationはTestFlightコードです。このTestFlightバグの詳細については、こちらをご覧ください:https://github.com/AFNetworking/AFNetworking/issues/307 – mackinra