【问题标题】:Dispatch queues and asynchronous RNCryptor调度队列和异步 RNCryptor
【发布时间】:2013-01-23 14:04:22
【问题描述】:

这是Asynchronously decrypt a large file with RNCryptor on iOS的后续行动

我已经设法使用本文中描述的方法异步解密一个下载的大型文件 (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 循环中盲目发送,但是我完全迷路了。我试过了:

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

这 4 种变体都不起作用。

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

【问题讨论】:

    标签: ios objective-c grand-central-dispatch rncryptor


    【解决方案1】:

    如果您只想一次处理一个块,则仅在第一个块回调您时处理一个块。您不需要信号量来执行此操作,您只需要在回调中执行下一次读取。您可能需要在 readStreamBlock 内添加一个 @autoreleasepool 块,但我认为您不需要它。

    当我有时间时,我可能会将其直接包装到 RNCryptor 中。我为此打开了Issue#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();
    

    【讨论】:

    • 谢谢 Rob,这很好用。我同意直接在 RNCryptor 中使用这样的异步方法会很方便,带有进度回调等等。
    • 当我解密 120 MB 文件时,此代码会导致巨大的 (120 MB) 内存峰值。
    • @RobNaiper 你能告诉我如何在 Rncryptor 中使用 pkdf2 吗?
    • @Jack 这似乎是一个新问题,我不确定您的目标是什么。您应该打开一个包含详细信息的新问题。
    • @RobNapier 感谢您的回复。实际上我在这里得到了一些东西 - briskbee.com/2018/06/using-pbkdf2-on-multiple-platforms.html
    【解决方案2】:

    西里尔,

    您的应用由于内存限制而崩溃的原因是 RNCryptor 缓冲区的增长超出了设备的能力。

    基本上,您读取文件内容的速度比 RNCryptor 处理它的速度要快得多。由于它不能足够快地解密,它会缓冲传入的流,直到它可以处理它。

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

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

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

    注意事项:

    • 我将 while 循环的内部封装在 @autoreleasepool 块中以强制释放数据。没有它,直到 while 循环结束才会释放。 (Matt Galloway 有一篇很棒的帖子在这里解释它:A Look under ARC's hood
    • 调用 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);
    
    }
    

    【讨论】:

    • 这是个好主意,但我认为您不需要信号量。您可以只读取回调中的数据以使其自我延续。我不认为 @autoreleasepool 实际上是必需的,但这是一个非常有趣的问题。我不得不更多地玩它。
    • 自多年前遇到信号量这个词以来,我第一次实际使用信号量。直到今天我才明白它们的用途。谢谢!
    【解决方案3】:

    在过去 2 天试图让我的 MBProgress hud 使用 Calman 的代码更新其进度后,我想出了以下内容。 使用的内存仍然很低,并且 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;
                });
    
                               }
                               }
    }
        });
    

    }

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-10-24
      • 2020-07-05
      • 1970-01-01
      • 1970-01-01
      • 2017-10-26
      • 1970-01-01
      • 1970-01-01
      • 2011-08-01
      相关资源
      最近更新 更多