Dispatch files d'attente et RNCryptor asynchronous

Ceci est un suivi de décrypter de manière asynchronous un gros file avec RNCryptor sur iOS

J'ai réussi à déchiffrer de manière asynchronous un gros file téléchargé (60Mb) avec la méthode décrite dans ce post, corrigé par Calman dans sa réponse.

Cela va essentiellement comme ceci:

int blockSize = 32 * 1024; NSInputStream *cryptedStream = [NSInputStream inputStreamWithFileAtPath:...]; NSOutputStream *decryptedStream = [NSOutputStream output...]; [cryptedStream open]; [decryptedStream open]; RNDecryptor *decryptor = [[RNDecryptor alloc] initWithPassword:@"blah" handler:^(RNCryptor *cryptor, NSData *data) { NSLog("Decryptor recevied %d bytes", data.length); [decryptedStream write:data.bytes maxLength:data.length]; if (cryptor.isFinished) { [decryptedStream close]; // call my delegate that I'm finished with decrypting } }]; while (cryptedStream.hasBytesAvailable) { uint8_t buf[blockSize]; NSUInteger bytesRead = [cryptedStream read:buf maxLength:blockSize]; NSData *data = [NSData dataWithBytes:buf length:bytesRead]; [decryptor addData:data]; NSLog("Sent %d bytes to decryptor", bytesRead); } [cryptedStream close]; [decryptor finish]; 

Cependant, je suis toujours confronté à un problème: l'set des données est chargé en memory avant d'être déchiffré. Je peux voir un tas de "Sent X octets à décrypter", et après cela, le même tas de "Decryptor recevied X octets" dans la console, quand je voudrais voir "Envoyé, reçu, envoyé, reçoit, .. ".

C'est bien pour les petits files (2Mb), ou avec de gros files (60Mb) sur simulateur; mais sur un vrai iPad1 il se bloque en raison de contraintes de memory, donc évidemment je ne peux pas garder cette procédure pour mon application de production.

Je sens que j'ai besoin d'envoyer datatables au décrypteur en utilisant dispatch_async au lieu de l'envoyer aveuglément dans la boucle while, mais je suis complètement perdu. J'ai essayé:

  • créer ma propre queue avant et en utilisant dispatch_async(myQueue, ^{ [decryptor addData:data]; });
  • la même chose, mais l'envoi de l'set du code à l'intérieur de la boucle while
  • la même chose, mais l'envoi de la boucle while entier
  • en utilisant RNCryptor responseQueue au lieu de ma propre queue

Rien ne fonctionne parmi ces 4 variantes.

Je n'ai pas encore une compréhension complète des files d'attente d'expédition; Je sens que le problème est ici. Je serais heureux si quelqu'un pouvait faire la lumière sur ce sujet.

Si vous ne voulez traiter qu'un seul bloc à la fois, ne traiter un bloc que lorsque le premier bloc vous callbackle. Vous n'avez pas besoin d'un sémaphore pour cela, il vous suffit d'effectuer la lecture suivante dans le callback. Vous pourriez vouloir un bloc @autoreleasepool intérieur de readStreamBlock , mais je ne pense pas que vous en ayez besoin.

Quand j'ai un peu de time, je vais probablement envelopper directement dans RNCryptor. J'ai ouvert le numéro 47 pour cela. Je suis ouvert à tirer des requests.

 // Make sure that this number is larger than the header + 1 block. // 33+16 bytes = 49 bytes. So it shouldn't be a problem. int blockSize = 32 * 1024; NSInputStream *cryptedStream = [NSInputStream inputStreamWithFileAtPath:@"C++ Spec.pdf"]; NSOutputStream *decryptedStream = [NSOutputStream outputStreamToFileAtPath:@"/tmp/C++.crypt" append:NO]; [cryptedStream open]; [decryptedStream open]; // We don't need to keep making new NSData objects. We can just use one repeatedly. __block NSMutableData *data = [NSMutableData dataWithLength:blockSize]; __block RNEncryptor *decryptor = nil; dispatch_block_t readStreamBlock = ^{ [data setLength:blockSize]; NSInteger bytesRead = [cryptedStream read:[data mutableBytes] maxLength:blockSize]; if (bytesRead < 0) { // Throw an error } else if (bytesRead == 0) { [decryptor finish]; } else { [data setLength:bytesRead]; [decryptor addData:data]; NSLog(@"Sent %ld bytes to decryptor", (unsigned long)bytesRead); } }; decryptor = [[RNEncryptor alloc] initWithSettings:kRNCryptorAES256Settings password:@"blah" handler:^(RNCryptor *cryptor, NSData *data) { NSLog(@"Decryptor recevied %ld bytes", (unsigned long)data.length); [decryptedStream write:data.bytes maxLength:data.length]; if (cryptor.isFinished) { [decryptedStream close]; // call my delegate that I'm finished with decrypting } else { // Might want to put this in a dispatch_async(), but I don't think you need it. readStreamBlock(); } }]; // Read the first block to kick things off readStreamBlock(); 

Cyrille,

La raison pour laquelle votre application plante en raison de contraintes de memory est que le tampon RNCryptor dépasse les capacités du périphérique.

Fondamentalement, vous lisez le contenu du file beaucoup plus rapidement que RNCryptor ne peut le gérer. Comme il ne peut pas décrypter assez rapidement, il tamponne le stream entrant jusqu'à ce qu'il puisse le traiter.

Je n'ai pas encore le time de plonger dans le code RNCryptor et de comprendre exactement comment GCD gère tout, mais vous pouvez utiliser un sémaphore pour forcer les lectures à attendre que le bloc précédent soit déchiffré.

Le code ci-dessous peut déchiffrer avec succès un file de 225 Mo sur un iPad 1 sans se bloquer.

Il y a quelques problèmes dont je ne suis pas très satisfait, mais cela devrait vous donner un bon sharepoint départ.

Quelques choses à noter:

  • J'ai enveloppé les internes de la boucle while dans un bloc @autoreleasepool pour forcer la publication des données. Sans cela, la libération ne se produira pas avant la fin de la boucle while. (Matt Galloway a un excellent post l'expliquant ici: Un regard sous le capot de l'ARC
  • L'appel à dispatch_semaphore_wait bloque l'exécution jusqu'à ce qu'un dispatch_semaphore_signal soit reçu. Cela signifie qu'il n'y a pas de mises à jour de l'interface user et que l'application risque de geler si vous en envoyez une de trop (donc la vérification de bytesRead> 0).

Personnellement, je pense qu'il doit y avoir une meilleure solution pour cela, mais je n'ai pas encore eu le time d'en faire un peu plus.

J'espère que ça aide.

 - (IBAction)decryptWithSemaphore:(id)sender { dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); __block int total = 0; int blockSize = 32 * 1024; NSArray *docPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); NSSsortingng *input = [[docPaths objectAtIndex:0] ssortingngByAppendingPathComponent:@"zhuge.rncryptor"]; NSSsortingng *output = [[docPaths objectAtIndex:0] ssortingngByAppendingPathComponent:@"zhuge.decrypted.pdf"]; NSInputStream *cryptedStream = [NSInputStream inputStreamWithFileAtPath:input]; __block NSOutputStream *decryptedStream = [NSOutputStream outputStreamToFileAtPath:output append:NO]; __block NSError *decryptionError = nil; [cryptedStream open]; [decryptedStream open]; RNDecryptor *decryptor = [[RNDecryptor alloc] initWithPassword:@"12345678901234567890123456789012" handler:^(RNCryptor *cryptor, NSData *data) { @autoreleasepool { NSLog(@"Decryptor recevied %d bytes", data.length); [decryptedStream write:data.bytes maxLength:data.length]; dispatch_semaphore_signal(semaphore); data = nil; if (cryptor.isFinished) { [decryptedStream close]; decryptionError = cryptor.error; // call my delegate that I'm finished with decrypting } } }]; while (cryptedStream.hasBytesAvailable) { @autoreleasepool { uint8_t buf[blockSize]; NSUInteger bytesRead = [cryptedStream read:buf maxLength:blockSize]; if (bytesRead > 0) { NSData *data = [NSData dataWithBytes:buf length:bytesRead]; total = total + bytesRead; [decryptor addData:data]; NSLog(@"New bytes to decryptor: %d Total: %d", bytesRead, total); dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); } } } [cryptedStream close]; [decryptor finish]; dispatch_release(semaphore); } 

Après avoir passé les deux derniers jours à essayer de faire en sorte que mon hud MBProgress mette à jour sa progression avec le code de Calman, j'ai trouvé ce qui suit. La memory utilisée rest faible et les mises à jour de l'interface user

 - (IBAction)decryptWithSemaphore:(id)sender { __block int total = 0; int blockSize = 32 * 1024; NSArray *docPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); NSSsortingng *input = [[docPaths objectAtIndex:0] ssortingngByAppendingPathComponent:@"zhuge.rncryptor"]; NSSsortingng *output = [[docPaths objectAtIndex:0] ssortingngByAppendingPathComponent:@"zhuge.decrypted.pdf"]; NSInputStream *cryptedStream = [NSInputStream inputStreamWithFileAtPath:input]; __block NSOutputStream *decryptedStream = [NSOutputStream outputStreamToFileAtPath:output append:NO]; __block NSError *decryptionError = nil; __block RNDecryptor *encryptor=nil; NSDictionary *atsortingbutes = [[NSFileManager defaultManager] atsortingbutesOfItemAtPath:input error:NULL]; __block long long fileSize = [atsortingbutes fileSize]; [cryptedStream open]; [decryptedStream open]; dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0); dispatch_async(queue, ^{ dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); encryptor = [[RNDecryptor alloc] initWithPassword:@"12345678901234567890123456789012" handler:^(RNCryptor *cryptor, NSData *data) { @autoreleasepool { NSLog(@"Decryptor recevied %d bytes", data.length); [decryptedStream write:data.bytes maxLength:data.length]; dispatch_semaphore_signal(semaphore); data = nil; if (cryptor.isFinished) { [decryptedStream close]; decryptionError = cryptor.error; [cryptedStream close]; [encryptor finish]; // call my delegate that I'm finished with decrypting } } }]; while (cryptedStream.hasBytesAvailable) { @autoreleasepool { uint8_t buf[blockSize]; NSUInteger bytesRead = [cryptedStream read:buf maxLength:blockSize]; if (bytesRead > 0) { NSData *data = [NSData dataWithBytes:buf length:bytesRead]; total = total + bytesRead; [encryptor addData:data]; NSLog(@"New bytes to decryptor: %d Total: %d", bytesRead, total); dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); dispatch_async(dispatch_get_main_queue(), ^{ HUD.progress = (float)total/fileSize; }); } } } }); 

}