2010-12-27 14 views
9

Pracuję nad aplikacją na iPhone'a, która polega na przesyłaniu pełnych zdjęć z aparatu (zazwyczaj od 1,5 do 2,0 MB każdy), jak również ich miniatur (znacznie mniejsze) do Amazon S3.Błąd POSIX 12 ("Nie można przydzielić pamięci") podczas przesyłania plików z iPhone'a

Miniatury zawsze poprawnie się ładują, ale czasami pełne obrazy nie, a gdy się nie powiedzie, nie działają z kodem błędu POSIX 12, inaczej ENOMEM. Jednak dodałem kod debugowania, aby wydrukować ilość wolnej pamięci, gdy wystąpi błąd, i zawsze jest trochę za darmo, zwykle ponad 100 MB.

Co więcej, błąd pojawia się częściej, gdy przesyłanie odbywa się przez 3G i mniej, gdy jest za dużo Wi-Fi - co wydaje się dziwne, ponieważ żądanie nie pobiera dużo, a plik, który jest przesyłany, jest już w pamięci (I Próbowałem również przesyłać strumieniowo z dysku bez poprawy.

Próbowałem przesłać plik przy użyciu NSURLConnection, funkcji Foundation CFHTTP * i biblioteki ASIHTTPRequest, ale niezależnie od tego błąd występuje z tą samą częstotliwością. Jeszcze dziwniejsze jest to, że wszyscy moi Googlingi ujawnili, że użytkownicy końcowi czasami otrzymują kod błędu 12 z Safari - nie widziałem żadnego programisty iOS, który o tym wspomniał. Pracuję z odziedziczoną bazą kodu, więc jest możliwe, że coś jest z nią nie tak, ale nie jestem nawet pewien, czego szukać. Każdy wgląd byłby bardzo doceniony!

+0

jakie połączenie powraca ENOMEM? –

+0

Kiedy korzystałem z NSURLConnection, otrzymywałbym błąd jako część metody delegowania didFailWithError - NSError z domeną błędu POSIX, kod błędu 12 i zlokalizowany opis "Can not allocate memory". Kiedy użyłem CFHTTPMessageRef, nie zadziałałoby to podczas wywoływania CFReadStreamRead() (funkcja zwrócona -1), a potem errno byłaby równa 12. –

+0

Widzę ten sam problem podczas próby przesłania plików za pomocą interfejsu Google Docs API w 3G. W poście na temat Dropbox API wspomniano o podobnym problemie: http://forums.dropbox.com/topic.php?id=25351. Wydaje się, że dzieje się to przy wolnych połączeniach (3G) z dużymi plikami. – Kamchatka

Odpowiedz

2

Jedynym sposobem udało mi się obejść w tym przypadku używa się bezpośrednio gniazd i ręcznie tworzy nagłówek HTTP. Więc mój kod Zamieszczanie aktualnie wygląda tak:

- (void)socketClose 
{ 
    [_inputStream setDelegate:nil]; 
    [_inputStream close]; 
    [_inputStream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode]; 
    SCR_RELEASE_SAFELY(_inputStream); 

    [_outputStream setDelegate:nil]; 
    [_outputStream close]; 
    [_outputStream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode]; 
    SCR_RELEASE_SAFELY(_outputStream); 

    SCR_RELEASE_SAFELY(_headerBuffer); 
} 

- (void)sendRequest 
{ 
    [self socketClose]; 
    SCR_RELEASE_SAFELY(_headerBuffer); 

    if (!_shouldCancel) 
    { 
     NSString *httpMessage = [NSString stringWithFormat:@"POST upload.php HTTP/1.1\r\n" 
           "Host:" 
#ifndef TESTBED 
           " %@" 
#endif 
           "\r\n" 
           "User-Agent: MyApp/3.0.0 CFNetwork/534 Darwin/10.7.0\r\n" 
           "Content-Length: %d\r\n" 
           "Accept: */*\r\n" 
           "Accept-Language: en-us\r\n" 
           "Accept-Encoding: gzip, deflate\r\n" 
           "Content-Type: application/x-www-form-urlencoded\r\n" 
           "Connection: keep-alive\r\n\r\n" 
           "data=" 
#ifndef TESTBED 
           , [self.serverUrl host] 
#endif 
           , _bytesToUpload]; 

     NSString *key = @"data="; 
     NSData *keyData = [key dataUsingEncoding:NSASCIIStringEncoding]; 
     _bytesToUpload -= [keyData length]; 
     _bytesToUpload = MAX(0, _bytesToUpload); 

     _headerBuffer = [[NSMutableData alloc] initWithData:[httpMessage dataUsingEncoding:NSUTF8StringEncoding]]; 

     _writtenDataBytes = 0; 

     CFStreamCreatePairWithSocketToHost(kCFAllocatorDefault 
              , (CFStringRef)[self.serverUrl host] 
#ifdef TESTBED 
              , 8888 
#else 
              , 80 
#endif 
              , (CFReadStreamRef *)(&_inputStream) 
              , (CFWriteStreamRef *)(&_outputStream)); 

     [_inputStream setDelegate:self]; 
     [_outputStream setDelegate:self]; 

     [_inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode]; 
     [_outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode]; 

     [_inputStream open]; 
     [_outputStream open]; 
    } 
} 

- (void)stream:(NSStream *)theStream handleEvent:(NSStreamEvent)streamEvent 
{ 
    if (_outputStream == theStream) 
    { 
     switch (streamEvent) 
     { 
      case NSStreamEventOpenCompleted: 
      { 
       [self regenerateTimeoutTimer]; 
       break; 
      } 
      case NSStreamEventHasSpaceAvailable: 
      { 
       SCR_RELEASE_TIMER(_timeoutTimer); 
       NSInteger length = _headerBuffer.length; 

       if (length > 0) 
       { 
        NSInteger written = [_outputStream write:(const uint8_t *)[_headerBuffer bytes] maxLength:length]; 
        NSInteger rest = length - written; 

        if (rest > 0) 
        { 
         memmove([_headerBuffer mutableBytes], (const uint8_t *)[_headerBuffer mutableBytes] + written, rest); 
        } 

        [_headerBuffer setLength:rest]; 
       } 
       else 
       { 
        const uint8_t *dataBytes = [_data bytes]; 

        while ([_outputStream hasSpaceAvailable] && (_writtenDataBytes < _bytesToUpload)) 
        { 
         NSInteger written = [_outputStream write:dataBytes 
                 maxLength:MIN(_dataLength, _bytesToUpload - _writtenDataBytes)]; 

         if (written > 0) 
         { 
          _writtenDataBytes += written; 
         } 
        } 
       } 

       [self regenerateTimeoutTimer]; 

       break; 
      } 
      case NSStreamEventErrorOccurred: 
      { 
       SCR_RELEASE_TIMER(_timeoutTimer); 
       [self reportError:[theStream streamError]];     
       break; 
      } 
      case NSStreamEventEndEncountered: 
      { 
       SCR_RELEASE_TIMER(_timeoutTimer); 
       [self socketClose]; 
       break; 
      } 
     } 
    } 
    else if (_inputStream == theStream) 
    { 
     switch (streamEvent) 
     { 
      case NSStreamEventHasBytesAvailable: 
      { 
       SCR_RELEASE_TIMER(_timeoutTimer); 

       /* Read server response here if you wish */ 

       [self socketClose]; 

       break; 
      } 
      case NSStreamEventErrorOccurred: 
      { 
       SCR_RELEASE_TIMER(_timeoutTimer); 
       [self reportError:[theStream streamError]]; 
       break; 
      } 
      case NSStreamEventEndEncountered: 
      { 
       SCR_RELEASE_TIMER(_timeoutTimer); 
       [self socketClose]; 
       break; 
      } 
     } 
    } 
} 

Chociaż ASIHTTPRequest może pracować tutaj, zdecydowaliśmy się odejść od takich zależności zarówno w celu uzyskania wydajności i utrzymać wszystko pod własną kontrolą dokładnie. Możesz użyć narzędzia Wireshark do debugowania tego rodzaju rzeczy.

+0

Co robi twój generator regeneracjiTimeout? Zadzwoń do [self stream: theStream handleEvent: streamEvent] i wykonaj wcześniej ...? – JOM

+0

regenerateTimeoutTimer po prostu resetuje timer, którego używam do określenia, kiedy upłynął limit czasu żądania. Tutaj mamy sens, jeśli otrzymamy nawet kilka bajtów, zaczynamy znowu czekać. Ale jeśli nie otrzymamy więcej bajtów w określonym czasie (tak długo, jak chcesz - konfigurowalne), nasza klasa generuje niestandardowy wyjątek przekroczenia limitu czasu, a więc nie czekamy na zawsze (czasami zdarza się to w przypadku błędów sieciowych więc weź jako obejście). –

+0

Po dłuższej pracy nad tym i wyświetleniu błędu "Nie mogę przydzielić pamięci", doszedłem do wniosku, że w pewnych okolicznościach może pojawić się nawet w powyższym przykładowym kodzie. Kluczową sprawą jest prawidłowe otwarcie gniazda. Jest to pokazane tutaj: https://developer.apple.com/library/ios/#samplecode/SimpleURLConnections/Introduction/Intro.html#//apple_ref/doc/uid/DTS40009245 –

1

Kluczem do obejścia tego problemu jest przesłanie pliku przy użyciu strumienia. Podczas korzystania NSMutableURLRequest, można to osiągnąć używając coś podobnego do następującego:

NSMutableURLRequest* request = [NSMutableURLRequest requestWithURL:url]; 
[request setHTTPBodyStream:[NSInputStream inputStreamWithFileAtPath:filePath]]; 

podczas korzystania ASIHTTPRequest, streaming plików odbywa się z tym:

ASIHTTPRequest* request = [ASIHTTPRequest requestWithURL:url]; 
[request setPostBodyFilePath:filePath]; 
rRequest.shouldStreamPostDataFromDisk = YES; 
+0

Nie zapomnij ustawić metody żądania na POST lub PUT. –

+0

Tak naprawdę to wypróbowałem. Wydaje się, że w każdym przypadku sposób na przesyłanie dużych plików może zmniejszyć częstotliwość błędów ENOMEM, ale nadal występują. Mimo to, dziękuję za wskazanie tego, jest to dobra technika. –

1

Rozwiązałem ten błąd, używając operacji dla żądania (NSMutableUrlConnection) z @autorelease{} dla funkcji głównej. NSPOXIS pojawia się tylko czasami.

- (void)main 
NSURLConnection* connection; 
    @autoreleasepool //urgently needed for 3G upload 
    { 

     self.currentRequest = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:@"test.php"]]; 
     [self.currentRequest setHTTPMethod:@"PUT"]; 

     [self.currentRequest setHTTPBody:self.data];//inpustStream doesn't work 

     connection = [NSURLConnection connectionWithRequest:self.currentRequest delegate:self]; 
     [connection start]; 

    }//end autorelease pool 

     do 
     { 

      [[NSRunLoop currentRunLoop] runMode: NSDefaultRunLoopMode beforeDate: [NSDate distantFuture]]; 
      if ([self isCancelled]) 
      { 
       connection   = nil; 
       isFailed = YES; 
       break; 
      } 
      self.status(statusUpdateMessage); 
     } 
     while (!isFailed && !isCompleted); 
     [timer invalidate];//test 
     timer = nil; 

//corresponding of status via blocks 
     self.completed(!isFailed); 
     self.status(isFailed ? errorMessage : @"Completed"); 
     if (isFailed) 
     { 
      self.failed(errorMessage != nil ? errorMessage : @"Undefined error"); 
     } 

     self.data = nil; 
     self.currentRequest = nil; 

     connection = nil; 

} 
Powiązane problemy