调度队列和异步RNCryptor

Cyr*_*lle 7 objective-c grand-central-dispatch ios rncryptor

这是在iOS上使用RNCryptor异步解密大文件的后续操作

我已经设法用这篇文章中描述的方法异步解密一个大的下载文件(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];
Run Code Online (Sandbox Code Playgroud)

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

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

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

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

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

我还没有完全了解调度队列; 我觉得问题出在这里.如果有人能够对此有所了解,我会很高兴.

Rob*_*ier 9

如果您只想一次处理一个块,则只在第一个块回调时处理块.你不需要信号量来做到这一点,你只需要在回调中执行下一次读取.你可能想要一个@autoreleasepoolreadStreamBlock,但我认为你不需要它.

当我有一些时间时,我可能会将它直接包装到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();
Run Code Online (Sandbox Code Playgroud)


小智 6

西里尔,

您的应用程序因内存限制而崩溃的原因是RNCryptor缓冲区的增长超出了设备的功能.

基本上,您正在以比RNCryptor可以处理它的速度更快的速度读取文件的内容.由于它无法足够快地解密,因此它会缓冲传入流,直到它可以处理它为止.

我还没有时间深入研究RNCryptor代码并弄清楚它是如何使用GCD来管理所有内容的,但是你可以使用信号量强制读取等到上一个块被解密.

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

它有一些我不太满意的问题,但它应该给你一个不错的起点.

有些事情需要注意:

  • 我将while循环的内部包装在@autoreleasepool块中以强制释放数据.没有它,只有while循环结束才会发布.(Matt Galloway在这里有一篇很好的文章解释它:在ARC的引擎盖下看一看
  • 对dispatch_semaphore_wait的调用阻止执行,直到收到dispatch_semaphore_signal.这意味着没有UI更新,并且如果您发送太多的应用程序冻结的可能性(因此检查bytesRead> 0).

就个人而言,我觉得必须有一个更好的解决方案,但我还没有时间去研究它.

我希望这有帮助.

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

}
Run Code Online (Sandbox Code Playgroud)