调度队列和asynchronousRNCryptor

这是在iOS上使用RNCryptorasynchronous解密大文件的后续步骤

我设法用本文描述的方法asynchronous解密一个大的,下载的文件(60Mb),Calman在他的回答中纠正了这个问题。

它基本上是这样的:

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

但是,我仍然面临一个问题:在解密之前,整个数据都被加载到内存中。 我可以看到一堆“发送X字节解密”,并在那之后,同样的一堆“解密器收到X字节”在控制台,当我想看到“发送,接收,发送,接收,.. “。

这对于小型(2Mb)文件或模拟器上大型(60Mb)的文件来说很好; 但在真正的iPad1上,由于内存限制而崩溃,所以显然我不能保留这个程序为我的生产应用程序。

我觉得我需要通过使用dispatch_async发送数据到解密器,而不是盲目地在while循环中发送,但是我完全丢失了。 我试过了:

  • 在此之前创build我自己的队列,并使用dispatch_async(myQueue, ^{ [decryptor addData:data]; });
  • 同样的,但调度while循环内的整个代码
  • 同样的,但调度整个while循环
  • 使用RNCryptor responseQueue而不是我自己的队列

这四种变体中没有任何作用。

我还没有完全理解发送队列, 我觉得问题在这里。 如果有人能够说明这一点,我会很高兴。

如果您只想一次处理一个块,则只有在第一个块回叫时才处理块。 你不需要一个信号量来做到这一点,你只需要执行callback中的下一个读取。 你可能想在@autoreleasepool里面有一个@autoreleasepool块,但我不认为你需要它。

当我有一些时间的时候,我可能会直接把它包装到RNCryptor中。 我为此打开了第47期。 我打开请求。

 // 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(); 

西里尔,

由于内存限制,应用程序崩溃的原因是RNCryptor缓冲区增长超出了设备的function。

基本上,你比RNCryptor可以更快地读取文件的内容。 由于它不能够足够快地解密,所以缓冲了inputstream直到它能够处理它。

我还没有时间深入到RNCryptor的代码中,弄清楚它是如何使用GCD来pipe理所有的东西,但是你可以使用一个信号来强制读取等待,直到先前的块被解密。

下面的代码可以成功解密iPad 1上的225MB文件而不会崩溃。

它有几个我不太满意的问题,但它应该给你一个体面的起点。

有些事情要注意:

  • 我在@autoreleasepool块中封装了while循环的内部部分以强制释放数据。 没有它,直到while循环结束才会释放。 (马特·加洛韦(Matt Galloway)在这里有一个很棒的post来解释它: 在ARC的引擎盖下看看
  • 对dispatch_semaphore_wait的调用将阻止执行,直到收到dispatch_semaphore_signal。 这意味着没有UI更新,如果发送一个太多(因此检查bytesRead> 0)应用程序的潜力。

就我个人而言,我觉得应该有一个更好的解决scheme,但是我还没有时间去研究一下。

我希望这有帮助。

 - (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); NSString *input = [[docPaths objectAtIndex:0] stringByAppendingPathComponent:@"zhuge.rncryptor"]; NSString *output = [[docPaths objectAtIndex:0] stringByAppendingPathComponent:@"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); } 

花了最后2天试图让我的MBProgress hud更新其与卡尔曼的代码的进展,我想出了以下内容。 使用的内存仍然很低,UI更新

 - (IBAction)decryptWithSemaphore:(id)sender { __block int total = 0; int blockSize = 32 * 1024; NSArray *docPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); NSString *input = [[docPaths objectAtIndex:0] stringByAppendingPathComponent:@"zhuge.rncryptor"]; NSString *output = [[docPaths objectAtIndex:0] stringByAppendingPathComponent:@"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 *attributes = [[NSFileManager defaultManager] attributesOfItemAtPath:input error:NULL]; __block long long fileSize = [attributes 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; }); } } } }); 

}