2013-03-11 8 views
12

(AGGIORNATO) questo è il problema in poche parole: in iOS voglio leggere un file di grandi dimensioni, elaborarlo su di esso (in questo caso codificare come Base64 string() e salvare in un file temporaneo sul dispositivo. ho messo su un NSInputStream di leggere da un file, poi nelNSInputStream interrompe l'esecuzione, a volte genera EXC_BAD_ACCESS

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

sto facendo la maggior parte del lavoro. per qualche ragione, a volte posso vedere il NSInputStream appena si ferma Lo so perché ho una linea

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

all'inizio del (void)stream:(NSStream *)stream handleEvent:(NSStreamEvent)eventCode ea volte vorrei solo vedere il risultato

stream <__NSCFInputStream: 0x1f020b00> got event 2 

(che corrisponde all'evento NSStreamEventHasBytesAvailable) e quindi nulla dopo. Non evento 10, che corrisponde a NSStreamEventEndEncountered, non a un evento di errore, niente! E a volte ho anche ottenuto un'eccezione EXC_BAD_ACCESS che non ho idea al momento come eseguire il debug. Qualsiasi aiuto sarebbe apprezzato.

Ecco l'implementazione. Tutto inizia quando mi ha colpito un pulsante "invia", che innesca:

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

Ecco 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] ; 
} 

Qui è setUpStreamsForInputFile chiama sopra:

- (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]; 
} 

Infine, è qui più logica si verifica:

- (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:{ 
      ... 
     } 
    } 
} 

Nota: quando ho inviato questo messaggio per la prima volta, avevo l'impressione sbagliata che il problema riguardasse l'utilizzo di GCD. Come da risposta di Rob qui sotto ho rimosso il codice GCD e il problema persiste.

risposta

19

Primo: nel codice originale, non si stava utilizzando un thread in background, ma il thread principale (dispatch_async ma sulla coda principale).

Quando si pianifica NSInputStream per l'esecuzione sul runloop predefinito (quindi, il runloop del thread principale), gli eventi vengono ricevuti quando il thread principale è in modalità predefinita (NSDefaultRunLoopMode).

Ma: se si seleziona, il runloop predefinito cambia modalità in alcune situazioni (ad esempio durante una scansione di UIScrollView e altri aggiornamenti dell'interfaccia utente). Quando il runloop principale è in una modalità diversa da NSDefaultRunLoopMode, i tuoi eventi non vengono ricevuti.

Il tuo vecchio codice, con il dispatch_async, era quasi buono (ma sposta gli aggiornamenti dell'interfaccia utente sul thread principale). Si deve aggiungere solo alcuni cambiamenti:

  • spedizione in background, con qualcosa di simile:

:

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 
}); 
  • iniziare un ciclo corsa su quel thread. Questo deve essere fatto alla fine (ultima riga) della chiamata spedizione asincrona, con questo codice

:

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

Prova e fammi sapere

EDIT - codice di esempio ha aggiunto:

Per essere più chiari, io copiare e incollare il codice originale aggiornamento:

- (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. 
    });  
} 

Dopo aver fatto questa modifica, il vostro: metodo di

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

, sarà chiamato sullo stesso thread in cui è stato avviato il ciclo di esecuzione, un thread in background: se è necessario aggiornare l'interfaccia utente, è importante che tu la spedizione di nuovo al thread principale.

informazioni extra:

Nel mio codice che uso dispatch_async in una coda sfondo casuale (partenza della spedizione il codice su uno dei thread in background disponibili, o iniziare uno nuovo, se necessario, tutte le "automagicamente") . Se preferisci, puoi iniziare il tuo thread invece di usare un invio asincrono.

Inoltre, non controllo se un runloop è già in esecuzione prima di inviare il messaggio "run" (ma è possibile verificarlo utilizzando il metodo currentMode, osservare il riferimento NSRunLoop per ulteriori informazioni). Non dovrebbe essere necessario perché ogni thread ha una sola istanza NSRunLoop associata, quindi inviare un'altra esecuzione (se già in esecuzione) non fa nulla di male :-)

È anche possibile evitare l'uso diretto di runLoops e passare a un GCD completo approccio, usando dispatch_source, ma non l'ho mai usato direttamente così non posso darti un "buon esempio di codice" ora

+0

Grazie, LombaX. Solo per essere sicuro di capire quello che stai dicendo. Nella mia domanda iniziale, stavo dipingendo "sendData" a dispatch_async (dispatch_get_global_queue (0, 0) ... e poi quando stavo impostando il flusso per leggere dal file stavo usando dispatch_async (dispatch_get_main_queue() ... You ' sto dicendo di mantenere entrambi e basta aggiungere [[NSRunLoop currentRunLoop] run]? Non sono sicuro di seguire la logica, quindi penso di essere più sicuro ... grazie mille! – PeterD

+0

è questo che intendevi? '- (void) setUpStreamsForInputFile : (NSString *) INPATH fileOutput: (NSString *) OUTPATH ​​{ \t self.p_iStream = ... \t [p_iStream setDelegate: self]; \t dispatch_async (dispatch_get_main_queue(),^{ \t \t [p_iStream scheduleInRunLoop: [ NSRunLoop currentRunLoop] forMode: NSDefaultRunLoopMode]; \t \t [p_iStream open]; \t \t ** [[NSRunLoop currentRunLoop] run]; ** \t}); } ' – PeterD

+0

E questo: ' - (IBAction) invia: (id) mittente { [p_spinner startAnimating]; dispatch_queue_t queue = dispatch_get_global_queue (DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); dispatch_async (coda,^{ [self sendData]; ** [[NSRunLoop currentRunLoop] run]; **}); } Mi dispiace per essere ottuso .. :) – PeterD

4

NSStream richiede un ciclo di esecuzione. GCD non ne fornisce uno. Ma non hai bisogno di GCD qui. NSStream è già asincrono. Basta usarlo sul thread principale; questo è quello per cui è stato progettato.

Stai anche facendo diverse interazioni UI su un thread in background. Non puoi farlo. Tutte le interazioni UI devono avvenire sul thread principale (che è facile se si rimuove il codice GCD).

Dove GCD può essere utile se la lettura e l'elaborazione dei dati richiedono molto tempo, è possibile passare tale operazione a GCD durante NSStreamEventHasBytesAvailable.

+0

Grazie! Lo farò, ma in realtà lo usavo prima senza GCD. Ho aggiunto GCD perché volevo che tutto il lavoro di lettura e scrittura da/sui file avvenisse in background. Invece, senza GCD, io così il mio filatore si anima e il registro mostra tutti gli eventi del flusso. Ho anche pensato, come hai detto, che l'NSStream dovesse accadere in modo asincrono, ma poi non sono sicuro di cosa stavo facendo male. Potrei aggiornare la mia domanda con una versione che avevo prima o meglio magari fare una domanda separata. Grazie per la risposta! – PeterD

+0

Quindi, Rob, proponi di avvolgere la logica in NSStreamEventHasBytesAvailable in dispatch_async (dispatch_get_main_queue(),^{...}); ? Non credo che la codifica di ogni blocco di 24000 dati richieda tempo per giustificare il sovraccarico di GCD ... – PeterD

+0

Dovrò rideterminare e aggiornare le mie domande. Ho rimosso il codice GCD e non solo vedo ancora lo stesso problema, ma ho anche ottenuto un'eccezione EXC_BAD_ACCESS. Qualcosa non va nella logica in generale ... – PeterD

Problemi correlati