2013-03-11 6 views
12

(ZAKTUALIZOWANY) jest to problem w pigułce: w iOS Chcę odczytać duży plik, zrobić na nim trochę przetwarzania (w tym konkretnym przypadku zakodować jako ciąg Base64() i zapisać do pliku tymczasowego na urządzeniu. I założyć NSInputStream do odczytu z pliku, a następnie wNSInputStream przestaje działać, czasami wyrzuca EXC_BAD_ACCESS

(void)stream:(NSStream *)stream handleEvent:(NSStreamEvent)eventCode 

robię większość pracy. z jakiegoś powodu, czasem widzę NSInputStream prostu zatrzymuje Wiem, ponieważ mam linię

NSLog(@"stream %@ got event %x", stream, (unsigned)eventCode); 

na początku (void)stream:(NSStream *)stream handleEvent:(NSStreamEvent)eventCode i czasami chciałbym tylko zobaczyć wyjście

stream <__NSCFInputStream: 0x1f020b00> got event 2 

(co odpowiada przypadku NSStreamEventHasBytesAvailable) i wtedy nic później. Nie wydarzenie 10, które odpowiada NSStreamEventEndEncountered, a nie zdarzenie błędu, nic! Czasami nawet dostaję wyjątek EXC_BAD_ACCESS, który nie mam w tej chwili pojęcia jak debugować. Każda pomoc będzie doceniona.

Oto realizacja. Wszystko zaczyna się, kiedy uderzył w przycisk "Wyślij", który wyzwala:

- (IBAction)submit:(id)sender {  
    [p_spinner startAnimating];  
    [self performSelector: @selector(sendData) 
      withObject: nil 
      afterDelay: 0]; 
} 

Oto sendData:

-(void)sendData{ 
    ... 
    _tempFilePath = ... ; 
    [[NSFileManager defaultManager] createFileAtPath:_tempFilePath contents:nil attributes:nil]; 
    [self setUpStreamsForInputFile: [self.p_mediaURL path] outputFile:_tempFilePath]; 
    [p_spinner stopAnimating]; 
    //Pop back to previous VC 
    [self.navigationController popViewControllerAnimated:NO] ; 
} 

Oto setUpStreamsForInputFile nazywa powyżej:

- (void)setUpStreamsForInputFile:(NSString *)inpath outputFile:(NSString *)outpath { 
    self.p_iStream = [[NSInputStream alloc] initWithFileAtPath:inpath]; 
    [p_iStream setDelegate:self]; 
    [p_iStream scheduleInRunLoop:[NSRunLoop currentRunLoop] 
          forMode:NSDefaultRunLoopMode]; 
    [p_iStream open]; 
} 

Wreszcie, to gdzie występuje większość logiki:

- (void)stream:(NSStream *)stream handleEvent:(NSStreamEvent)eventCode { 

    NSLog(@"stream %@ got event %x", stream, (unsigned)eventCode); 

    switch(eventCode) { 
     case NSStreamEventHasBytesAvailable: 
     { 
      if (stream == self.p_iStream){ 
       if(!_tempMutableData) { 
        _tempMutableData = [NSMutableData data]; 
       } 
       if ([_streamdata length]==0){ //we want to write to the buffer only when it has been emptied by the output stream 
        unsigned int buffer_len = 24000;//read in chunks of 24000 
        uint8_t buf[buffer_len]; 
        unsigned int len = 0; 
        len = [p_iStream read:buf maxLength:buffer_len]; 
        if(len) { 
         [_tempMutableData appendBytes:(const void *)buf length:len]; 
         NSString* base64encData = [Base64 encodeBase64WithData:_tempMutableData]; 
         _streamdata = [base64encData dataUsingEncoding:NSUTF8StringEncoding]; //encode the data as Base64 string 
         [_tempFileHandle writeData:_streamdata];//write the data 
         [_tempFileHandle seekToEndOfFile];// and move to the end 
         _tempMutableData = [NSMutableData data]; //reset mutable data buffer 
         _streamdata = [[NSData alloc] init]; //release the data buffer 
        } 
       } 
      } 
      break; 
     case NSStreamEventEndEncountered: 
     { 
      [stream close]; 
      [stream removeFromRunLoop:[NSRunLoop currentRunLoop] 
           forMode:NSDefaultRunLoopMode]; 
      stream = nil; 
      //do some more stuff here... 
      ... 
      break; 
     } 
     case NSStreamEventHasSpaceAvailable: 
     case NSStreamEventOpenCompleted: 
     case NSStreamEventNone: 
     { 
      ... 
     } 
     } 
     case NSStreamEventErrorOccurred:{ 
      ... 
     } 
    } 
} 

Uwaga: kiedy opublikowałem to jako pierwsze, miałem złe wrażenie, że problem miał coś wspólnego z używaniem GCD. Zgodnie z odpowiedzią Roba poniżej usunąłem kod GCD, a problem nadal występuje.

Odpowiedz

19

Po pierwsze: w oryginalnym kodzie nie używałeś wątku tła, ale głównego wątku (dispatch_async, ale w głównej kolejce).

Po zaplanowaniu uruchomienia NSInputStream na domyślnym runlerze (tak, runloop głównego wątku), zdarzenia są odbierane, gdy główny wątek jest w trybie domyślnym (NSDefaultRunLoopMode).

Ale: jeśli zaznaczysz, domyślny tryb zmian runloopa w niektórych sytuacjach (na przykład podczas przewijania UIScrollView i innych aktualizacji interfejsu użytkownika). Kiedy główny runloop jest w trybie innym niż NSDefaultRunLoopMode, twoje zdarzenia nie są odbierane.

Twój stary kod z dispatch_async był prawie niezły (ale przenieś aktualizacje interfejsu na główny wątek). Trzeba dodać tylko kilka zmian:

  • wysyłkę w tle, z czegoś takiego:

:

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0); 
dispatch_async(queue, ^{ 
    // your background code 

    //end of your code 

    [[NSRunLoop currentRunLoop] run]; // start a run loop, look at the next point 
}); 
  • uruchomić pętlę uruchomić na tym wątku. To musi być zrobione na końcu (ostatnia linia) wywołania wysyłka asynchronicznym, z tym kodem

:

[[NSRunLoop currentRunLoop] run]; // note: this method never returns, so it must be THE LAST LINE of your dispatch 

Spróbuj i daj mi znać

EDIT - dodaje przykładowy kod:

Aby być bardziej jasne, skopiować i wkleić oryginalny kod aktualizacja:

- (void)setUpStreamsForInputFile:(NSString *)inpath outputFile:(NSString *)outpath { 
    self.p_iStream = [[NSInputStream alloc] initWithFileAtPath:inpath]; 
    [p_iStream setDelegate:self]; 

    // here: change the queue type and use a background queue (you can change priority) 
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0); 
    dispatch_async(queue,^{ 
     [p_iStream scheduleInRunLoop:[NSRunLoop currentRunLoop] 
         forMode:NSDefaultRunLoopMode]; 
     [p_iStream open]; 

     // here: start the loop 
     [[NSRunLoop currentRunLoop] run]; 
     // note: all code below this line won't be executed, because the above method NEVER returns. 
    });  
} 

Po dokonaniu tej zmiany, waszym

- (void)stream:(NSStream *)stream handleEvent:(NSStreamEvent)eventCode {} 

metoda zostanie wywołana w tym samym wątku, w którym rozpoczął pętlę biegać, wątek tła: jeśli chcesz zaktualizować UI, to ważne, aby ponownie wysyłką do głównego wątku.

Dodatkowe informacje:

W moim kodu używam dispatch_async w kolejce losowo tła (która wysyła kod na jednym z dostępnych wątków tła, lub rozpocząć nową, jeżeli to konieczne, wszystko „automagicznie”) . Jeśli wolisz, możesz uruchomić własny wątek, zamiast korzystać z asynchronizacji wysyłki.

Co więcej, nie sprawdzam, czy runloop jest już uruchomiony przed wysłaniem komunikatu "run" (ale można to sprawdzić przy użyciu metody currentMode, zapoznaj się z referencją NSRunLoop, aby uzyskać więcej informacji). Nie powinno być to konieczne, ponieważ każdy wątek ma tylko jedną powiązaną instancję NSRunLoop, więc wysłanie kolejnego uruchomienia (jeśli jest już uruchomione) nie robi nic złego :-)

Można nawet uniknąć bezpośredniego użycia runLoops i przejść do kompletnego GCD podejście, używając dispatch_source, ale nigdy nie użyłem go bezpośrednio, więc nie mogę dać ci "dobrego przykładowego kodu" teraz

+0

Dzięki, LombaX. Aby upewnić się, że rozumiem, co mówisz. W moim oryginalnym pytaniu, byłem dipatching "sendData" do dispatch_async (dispatch_get_global_queue (0, 0) ... a następnie, gdy byłem konfiguracji strumienia do odczytu z pliku użyłem dispatch_async (dispatch_get_main_queue() ... You ' ponownie mówię, aby zachować zarówno i po prostu dodać [[NSRunLoop currentRunLoop] uruchomić]? Nie jestem pewien, że postępuję zgodnie z logiką, więc myślę, że lepiej upewnić się ... wielkie dzięki! – PeterD

+0

jest to, co masz na myśli? '- (void) setUpStreamsForInputFile (NSString *) inpath oUTPUTFILE (NSString *) outpath { \t self.p_iStream = ... \t [p_iStream setDelegate Samodzielny]; \t dispatch_async (dispatch_get_main_queue()^{ \t \t [p_iStream scheduleInRunLoop [ NSRunLoop currentRunLoop] forMode: NSDefaultRunLoopMode]; \t \t [p_iStream otwórz]; \t \t ** [[NSRunLoop currentRunLoop] run]; ** \t}); } ' – PeterD

+0

A to: ' - (IBAction) przesłać: (identyfikator) nadawca { [p_spinner startAnimating]; dispatch_queue_t queue = dispatch_get_global_queue (DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); dispatch_async (kolejka,^{ [self sendData]; ** [[NSRunLoop currentRunLoop] run]; **}); } ' } Przepraszamy za bycie rozwartym .. :) – PeterD

4

NSStream wymaga pętli uruchamiania. GCD tego nie zapewnia. Ale nie potrzebujesz tutaj GCD. NSStream jest już asynchroniczny. Po prostu użyj go na głównym wątku; właśnie do tego jest przeznaczony.

Wykonujesz również kilka interakcji interfejsu użytkownika w wątku tła. Nie możesz tego zrobić. Wszystkie interakcje interfejsu użytkownika muszą występować w głównym wątku (co jest łatwe, jeśli usuniesz kod GCD).

Gdzie GCD może być przydatny, jeśli odczytywanie i przetwarzanie danych jest czasochłonne, możesz przekazać tę operację do GCD podczas NSStreamEventHasBytesAvailable.

+0

Dzięki! Zrobię to, ale w rzeczywistości używałem go wcześniej bez GCD. Dodałem GCD, ponieważ chciałem, aby całe zadanie czytania i pisania z/do plików działo się w tle. Zamiast tego, bez GCD, animuję i obracam animację, pokazując wszystkie zdarzenia strumieniowe. Pomyślałem również, jak powiedziałeś, że NSStream powinien się odbywać asynchronicznie, ale nie jestem pewien, co robiłem źle. Mogę zaktualizować moje pytanie wersją, którą miałem wcześniej lub lepiej, może zadać osobne pytanie. Dziękuję za odpowiedź! – PeterD

+0

Więc, Rob, czy proponujesz zawijanie logiki w NSStreamEventHasBytesAvailable w dispatch_async (dispatch_get_main_queue(),^{...}); ? Nie sądzę, że kodowanie każdego bloku danych 24 000 jest czasochłonne, aby usprawiedliwić narzut GCD ... – PeterD

+0

Będę musiał poprawić i zaktualizować moje pytania. Usunąłem kod GCD i nie tylko nadal widzę ten sam problem, ale także właśnie dostałem wyjątek EXC_BAD_ACCESS. Coś jest nie tak z logiką w ogóle ... – PeterD