2013-03-11 9 views
9

Sto cercando di utilizzare RNCryptor per crittografare e decrittografare file di grandi dimensioni (600 + MB) su iOS. Su the github ho trovato codice di esempio su come utilizzare la libreria in modo asincrono sugli stream. Questo codice è simile alla risposta di Rob Napier su a question about this same subject.Problemi di memoria durante la crittografia/decrittografia di un file di grandi dimensioni con RNCryptor su iOS

Tuttavia, anche se penso di aver implementato correttamente il codice, l'app utilizza fino a 1,5 GB di memoria (nel simulatore di iPad 6.1). Pensavo che il codice avrebbe dovuto impedire all'app di mantenere più di un blocco di dati in memoria? Allora, cosa sta andando male?

Nel mio controller, creo un "CryptController" che messaggio con le richieste di crittografia/decrittografia.

// Controller.m 
    NSString *password = @"pw123"; 
    self.cryptor = [[CryptController alloc] initWithPassword:password]; 

    //start encrypting file 
    [self.cryptor streamEncryptRequest:self.fileName andExtension:@"pdf" withURL:[self samplesURL]]; 

    //wait for encryption to finish 
    NSDate *timeout = [NSDate dateWithTimeIntervalSinceNow:1]; 
    do { 
    [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode 
          beforeDate:timeout]; 
    } while (![self.cryptor isFinished]); 

In CryptController ho:

- (void)streamEncryptionDidFinish { 
    if (self.cryptor.error) { 
    NSLog(@"An error occurred. You cannot trust decryptedData at this point"); 
    } 
    else { 
    NSLog(@"%@ is complete. Use it as you like", [self.tempURL lastPathComponent]); 
    } 
    self.cryptor = nil; 
    self.isFinished = YES; 
} 

- (void) streamEncryptRequest:(NSString *)fileName andExtension:(NSString *)ext withURL:(NSURL *)directory { 

    //Make sure that this number is larger than the header + 1 block. 
    int blockSize = 32 * 1024; 

    NSString *encryptedFileName = [NSString stringWithFormat:@"streamEnc_%@", fileName]; 
    self.tempURL = [[NSFileManager defaultManager] URLForDirectory:NSDocumentDirectory inDomain:NSUserDomainMask appropriateForURL:nil create:NO error:nil]; 
    self.tempURL = [self.tempURL URLByAppendingPathComponent:encryptedFileName isDirectory:NO]; 
    self.tempURL = [self.tempURL URLByAppendingPathExtension:@"crypt"]; 

    NSInputStream *decryptedStream = [NSInputStream inputStreamWithURL:[[directory URLByAppendingPathComponent:fileName isDirectory:NO] URLByAppendingPathExtension:ext]]; 
    NSOutputStream *cryptedStream = [NSOutputStream outputStreamWithURL:self.tempURL append:NO]; 

    [cryptedStream open]; 
    [decryptedStream open]; 

    __block NSMutableData *data = [NSMutableData dataWithLength:blockSize]; 
    __block RNEncryptor *encryptor = nil; 

    dispatch_block_t readStreamBlock = ^{ 
    [data setLength:blockSize]; 
    NSInteger bytesRead = [decryptedStream read:[data mutableBytes] maxLength:blockSize]; 
    if (bytesRead < 0) { 
     //Throw an error 
    } 
    else if (bytesRead == 0) { 
     [encryptor finish]; 
    } 
    else { 
     [data setLength:bytesRead]; 
     [encryptor addData:data]; 
     //NSLog(@"Sent %ld bytes to encryptor", (unsigned long)bytesRead); 
    } 
    }; 

    encryptor = [[RNEncryptor alloc] initWithSettings:kRNCryptorAES256Settings 
              password:self.password 
              handler:^(RNCryptor *cryptor, NSData *data) { 
               //NSLog(@"Encryptor received %ld bytes", (unsigned long)data.length); 
               [cryptedStream write:data.bytes maxLength:data.length]; 
               if (cryptor.isFinished) { 
               [decryptedStream close]; 
               //call my delegate that i'm finished with decrypting 
               [self streamEncryptionDidFinish]; 
               } 
               else { 
               readStreamBlock(); 
               } 
              }]; 

    // Read the first block to kick things off 
    self.isFinished = NO; 
    readStreamBlock(); 
} 

Quando ho profilo utilizzando lo strumento di allocazione, le categorie di allocazione vedo costantemente in crescita sono malloc 32.50 KB, malloc 4.00 KB, NSConcreteData e NSSubrangeData. Soprattutto il malloc 32.50 KB diventa grande, oltre 1 GB. Il chiamante responsabile è [NSConcreteData initWithBytes:length:copy:freeWhenDone:bytesAreVM:] Per NSConcreteData il chiamante responsabile è -[NSData(NSData) copyWithZone:].

Quando profilo utilizzando lo strumento Leaks, non vengono rilevate perdite.

Sono nuovo di Objective-C, e da quello che ho capito, il nuovo ARC dovrebbe gestire l'allocazione e la deallocazione della memoria. Quando si cerca su qualsiasi cosa relativa alla memoria, tutte le informazioni che trovo presuppongono che tu non usi ARC (o che non esistesse al momento della scrittura). Sono sicuro che sto usando ARC, dal momento che mi vengono compilati errori che lo dicono quando provo a deallocare manualmente la memoria.

Se qualcuno mi può aiutare con questo, sarebbe molto apprezzato! Se sono necessarie ulteriori informazioni, sarò felice di fornirlo :) Inoltre, sono nuovo di StackOverflow, quindi se c'è qualcosa che ho trascurato che avrei dovuto fare, gentilmente informami!

+0

Ho appena provato la lettura e la scrittura di corsi d'acqua, solo per assicurarsi che il problema di memoria non è da qualche parte in là. La memoria non superava 1 MB, quindi il problema è con il codice di crittografia da qualche parte. – Johanneke

risposta

7

Ho finalmente provato la soluzione data here, che utilizza i semafori invece di dipendere dal callback per attendere lo streaming. Funziona perfettamente :) L'utilizzo della memoria è di circa 1,1 MB secondo lo strumento Allocations.Potrebbe non sembrare pulito a causa della sintassi del semaforo, ma almeno fa quello che mi serve per farlo.

Altri suggerimenti sono ancora benvenuti ovviamente :)

- (void)encryptWithSemaphore:(NSURL *)url { 
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); 

    __block int total = 0; 
    int blockSize = 32 * 1024; 

    NSString *encryptedFile = [[url lastPathComponent] stringByDeletingPathExtension]; 
    NSURL *docsURL = [[NSFileManager defaultManager] URLForDirectory:NSDocumentDirectory inDomain:NSUserDomainMask appropriateForURL:nil create:NO error:nil]; 
    self.tempURL = [[docsURL URLByAppendingPathComponent:encryptedFile isDirectory:NO] URLByAppendingPathExtension:@"crypt"]; 

    NSInputStream *inputStream = [NSInputStream inputStreamWithURL:url]; 
    __block NSOutputStream *outputStream = [NSOutputStream outputStreamWithURL:self.tempURL append:NO]; 
    __block NSError *encryptionError = nil; 

    [inputStream open]; 
    [outputStream open]; 

    RNEncryptor *encryptor = [[RNEncryptor alloc] initWithSettings:kRNCryptorAES256Settings 
                 password:self.password 
                 handler:^(RNCryptor *cryptor, NSData *data) { 
                  @autoreleasepool { 
                  [outputStream write:data.bytes maxLength:data.length]; 
                  dispatch_semaphore_signal(semaphore); 

                  data = nil; 
                  if (cryptor.isFinished) { 
                   [outputStream close]; 
                   encryptionError = cryptor.error; 
                   // call my delegate that I'm finished with decrypting 
                  } 
                  } 
                 }]; 
    while (inputStream.hasBytesAvailable) { 
    @autoreleasepool { 
     uint8_t buf[blockSize]; 
     NSUInteger bytesRead = [inputStream read:buf maxLength:blockSize]; 
     if (bytesRead > 0) { 
     NSData *data = [NSData dataWithBytes:buf length:bytesRead]; 

     total = total + bytesRead; 
     [encryptor addData:data]; 

     dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); 
     } 
    } 
    } 

    [inputStream close]; 
    [encryptor finish]; 
} 
0

Potrei sbagliarmi, ma penso che il ciclo do...while impedisca al pool di autorelease di svuotarsi abbastanza frequentemente.

Perché stai utilizzando questo ciclo per attendere il completamento del decryptor? Dovresti usare il blocco di completamento per notificare al tuo controller che il drcryptor ha finito, invece.

(BTW, benvenuto in SO, la vostra domanda è molto ben fatta e questo è molto apprezzato).

+0

Sto usando il ciclo, perché l'ho visto in un esempio di codice RNCryptor, [testAsync] (https://github.com/rnapier/RNCryptor/blob/master/RNCryptorTests/RNCryptorTests.m#L168). Come si comunica al controller? E questo significa che il controller dovrebbe semplicemente attendere la notifica senza eseguire il polling del booleano? – Johanneke

+0

Sì, aspetta solo il blocco di completamento per attivare alcuni metodi del controller. Puoi anche usare NSNotifications, ma attualmente hai un '[self streamEncryptionDidFinish]' che sembra fare il lavoro. – Cyrille

+0

Ok, grazie. Lo proverò e vedrò se risolve il problema. Quando ne saprò di più, posterò di nuovo qui. – Johanneke

0

potrei ancora essere sbagliato, ma nel tuo readStreamBlock, data dovrebbe essere un parametro al blocco, piuttosto che un riferimento a un __block NSMutableData dichiarata al di fuori di esso. Come puoi vedere, il gestore RNEncryptor fornisce la sua variabile data, che è diversa da quella che hai dichiarato tu stesso.

Idealmente, metti tutti i tuoi readStreamBlock direttamente all'interno del gestore, senza nemmeno dichiararlo un blocco.

+0

Penso che la variabile 'data' in' readStreamBlock' è ciò che viene passato al gestore, invece del contrario. In 'readStreamBlock', un blocco dall'inputstream viene letto nei dati, dopodiché viene chiamato' [encryptor addData: data] '. Ho pensato che questo significasse che 'data' è passato al gestore qui, quindi può essere crittografato e scritto su file usando outputstream. E potrei mettere tutto 'readStreamBlock' direttamente nel gestore, ma poi avrei bisogno di duplicare del codice per avviare l'intero processo. Come posso chiamare semplicemente 'readStreamBlock' (come faccio alla fine del metodo). – Johanneke

+0

Ma proverò a giocare con la variabile 'data' e vedere se questo aiuta. Semplicemente non penso che lo farà. – Johanneke

+0

Il gestore accetta solo argomenti 'NSData', non' NSMutableData' ... – Johanneke

4

basta eseguire:

self.cryptorQueue = dispatch_queue_create([queueName cStringUsingEncoding:NSUTF8StringEncoding], NULL); 

dispatch_async(self.cryptorQueue, ^{ 
     readStreamBlock(); 
    }); 

guai: Pila sta crescendo e autorelease tiro non verranno eseguiti immissione in buffer.

soluzione: aggiungi async nella stessa coda .. questo consentirà al blocco corrente di terminare l'esecuzione.

Ecco il codice:

__block NSMutableData *data = [NSMutableData dataWithLength:blockSize]; 
__block RNDecryptor *decryptor = nil; 


dispatch_block_t readStreamBlock = ^{ 

    [data setLength:blockSize]; 

    NSInteger bytesRead = [inputStream read:[data mutableBytes] maxLength:blockSize]; 
    if (bytesRead < 0) { 
     // Throw an error 
    } 
    else if (bytesRead == 0) { 
     [decryptor finish]; 
    } 
    else { 

     [data setLength:bytesRead]; 
     [decryptor addData:data]; 
    } 
}; 

decryptor = [[RNDecryptor alloc] initWithPassword:@"blah" handler:^(RNCryptor *cryptor, NSData *data) { 

     [decryptedStream write:data.bytes maxLength:data.length]; 
     _percentStatus = (CGFloat)[[decryptedStream propertyForKey:NSStreamFileCurrentOffsetKey] intValue]/(CGFloat)_inputFileSize; 
     if (cryptor.isFinished) 
     { 
      [decryptedStream close]; 
      [self decryptFinish]; 
     } 
     else 
     { 
      dispatch_async(cryptor.responseQueue, ^{ 
       readStreamBlock(); 
      }); 
      [self decryptStatusChange]; 
     } 

}]; 


// Read the first block to kick things off 

decryptor.responseQueue = self.cryptorQueue; 
[self decryptStart]; 
dispatch_async(decryptor.cryptorQueue, ^{ 
    readStreamBlock(); 
}); 
+0

Ero in esecuzione lo stesso problema del OP e questo lo ha risolto per me, in particolare mettendo 'readStreamBlock()' in una chiamata asincrona. Ciò consente al thread di tornare al ciclo principale, che consente di autorizzare i blocchi di dati prima di leggere il successivo blocco di dati. Grazie! –

+0

Grazie per questo codice: funziona bene e risolve il problema dei picchi di memoria. Ma sto ancora avendo problemi con il download di file di grandi dimensioni e la crittografia nello stesso momento in cui RNCryptor non supporta gli stream. Qualcuno l'ha risolto? –

Problemi correlati