7

これは、 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];

ただし、まだ問題に直面しています。データ全体が復号化される前にメモリに読み込まれます。「送信、受信、送信、受信、.. .」。

小さな (2Mb) ファイル、またはシミュレータ上の大きな (60Mb) ファイルの場合は問題ありません。しかし、実際の iPad1 ではメモリの制約によりクラッシュするため、明らかにこの手順を実稼働アプリに適用することはできません。

ループdispatch_asyncでやみくもにデータを送信するのではなく、使用してデクリプターにデータを送信する必要があるように感じますが、完全に失われています。while私はもう試した:

  • の前に独自のキューを作成し、while使用するdispatch_async(myQueue, ^{ [decryptor addData:data]; });
  • while同じですが、ループ内でコード全体をディスパッチします
  • while同じですが、ループ全体をディスパッチします
  • 自分のキューの代わりにRNCryptor-providedを使用するresponseQueue

これら 4 つのバリアントの中では何も機能しません。

ディスパッチ キューについてはまだ完全には理解していません。ここに問題があると感じています。誰かがこれに光を当てることができれば幸いです。

4

3 に答える 3

9

一度に 1 つのブロックのみを処理する場合は、最初のブロックからコールバックされたときにのみブロックを処理します。そのためにセマフォは必要ありません。コールバック内で次の読み取りを実行するだけで済みます。@autoreleasepoolの中にブロックが必要かもしれませんがreadStreamBlock、必要ないと思います。

時間があれば、おそらくこれを 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();
于 2013-01-29T15:21:02.097 に答える
6

シリル、

メモリの制約によりアプリがクラッシュする理由は、RNCryptor バッファがデバイスの能力を超えて大きくなるためです。

基本的に、RNCryptor が処理できるよりもはるかに速くファイルの内容を読み取っています。十分な速度で復号化できないため、受信ストリームを処理できるようになるまでバッファリングします。

RNCryptor コードに飛び込んで、GCD を使用してすべてを管理する方法を正確に把握する時間はまだありませんが、セマフォを使用して、前のブロックが復号化されるまで読み取りを強制的に待機させることができます。

以下のコードは、クラッシュすることなく、iPad 1 で 225MB のファイルを正常に復号化できます。

私が満足していない問題がいくつかありますが、それはあなたにまともな出発点を与えるはずです.

注意すべき点:

  • while ループの内部を @autoreleasepool ブロッ​​クでラップして、データを強制的に解放しました。これがないと、while ループが終了するまで解放されません。(Matt Galloway は、ここでそれを説明する素晴らしい投稿をしています: A Look under ARC's hoods
  • dispatch_semaphore_wait の呼び出しは、dispatch_semaphore_signal が受信されるまで実行をブロックします。これは、UI の更新がないことを意味し、1 つ送信しすぎるとアプリがフリーズする可能性があります (したがって、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);

}
于 2013-01-28T21:47:31.520 に答える
2

MBProgress hud の進行状況を Calman のコードで更新するために過去 2 日間を費やした後、次のことを思いつきました。使用されるメモリはまだ少ないままで、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;
            });

                           }
                           }
}
    });

}

于 2013-04-24T02:00:28.797 に答える