9

RNCryptor を使用して、iOS で大きなファイル (600 MB 以上) を暗号化および復号化しようとしています。githubでストリームでライブラリを非同期的に使用する方法のサンプル コードを見つけました。このコードは、この同じ件名に関する質問に対するRob Napier の回答に似ています。

ただし、コードを正しく実装したと思いますが、アプリは最大 1.5 GB のメモリを使用します (iPad 6.1 シミュレーターで)。このコードは、アプリがメモリ内に複数のデータ ブロックを保持するのを防ぐためのものだと思っていましたか? それで、何がうまくいかないのですか?

私のコントローラーでは、暗号化/復号化要求でメッセージを送る「CryptController」を作成します。

  // Controller.m
  NSString *password = @"pw123";
  self.cryptor = [[CryptController alloc] initWithPassword:password];

  //start encrypting file
  [self.cryptor streamEncryptRequest:self.fileName andExtension:@"pdf" withURL:[self samplesURL]];

  //wait for encryption to finish
  NSDate *timeout = [NSDate dateWithTimeIntervalSinceNow:1];
  do {
    [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
                             beforeDate:timeout];
  } while (![self.cryptor isFinished]);

CryptController には次のものがあります。

- (void)streamEncryptionDidFinish {
  if (self.cryptor.error) {
    NSLog(@"An error occurred. You cannot trust decryptedData at this point");
  }
  else {
    NSLog(@"%@ is complete. Use it as you like", [self.tempURL lastPathComponent]);
  }
  self.cryptor = nil;
  self.isFinished = YES;
}

- (void) streamEncryptRequest:(NSString *)fileName andExtension:(NSString *)ext withURL:(NSURL *)directory {

  //Make sure that this number is larger than the header + 1 block.
  int blockSize = 32 * 1024;

  NSString *encryptedFileName = [NSString stringWithFormat:@"streamEnc_%@", fileName];
  self.tempURL = [[NSFileManager defaultManager] URLForDirectory:NSDocumentDirectory inDomain:NSUserDomainMask appropriateForURL:nil create:NO error:nil];
  self.tempURL = [self.tempURL URLByAppendingPathComponent:encryptedFileName isDirectory:NO];
  self.tempURL = [self.tempURL URLByAppendingPathExtension:@"crypt"];

  NSInputStream *decryptedStream = [NSInputStream inputStreamWithURL:[[directory URLByAppendingPathComponent:fileName isDirectory:NO] URLByAppendingPathExtension:ext]];
  NSOutputStream *cryptedStream = [NSOutputStream outputStreamWithURL:self.tempURL append:NO];

  [cryptedStream open];
  [decryptedStream open];

  __block NSMutableData *data = [NSMutableData dataWithLength:blockSize];
  __block RNEncryptor *encryptor = nil;

  dispatch_block_t readStreamBlock = ^{
    [data setLength:blockSize];
    NSInteger bytesRead = [decryptedStream read:[data mutableBytes] maxLength:blockSize];
    if (bytesRead < 0) {
      //Throw an error
    }
    else if (bytesRead == 0) {
      [encryptor finish];
    }
    else {
      [data setLength:bytesRead];
      [encryptor addData:data];
      //NSLog(@"Sent %ld bytes to encryptor", (unsigned long)bytesRead);
    }
  };

  encryptor = [[RNEncryptor alloc] initWithSettings:kRNCryptorAES256Settings
                                           password:self.password
                                            handler:^(RNCryptor *cryptor, NSData *data) {
                                              //NSLog(@"Encryptor received %ld bytes", (unsigned long)data.length);
                                              [cryptedStream write:data.bytes maxLength:data.length];
                                              if (cryptor.isFinished) {
                                                [decryptedStream close];
                                                //call my delegate that i'm finished with decrypting
                                                [self streamEncryptionDidFinish];
                                              }
                                              else {
                                                readStreamBlock();
                                              }
                                            }];

  // Read the first block to kick things off
  self.isFinished = NO;
  readStreamBlock();
}

Allocation Instrument を使用してプロファイリングすると、一貫して増加している割り当てカテゴリはmalloc 32.50 KBmalloc 4.00 KBNSConcreteDataおよびNSSubrangeDataです。特に はmalloc 32.50 KB大きくなり、1 GB を超えます。担当の発信 者[NSConcreteData initWithBytes:length:copy:freeWhenDone:bytesAreVM:] はですNSConcreteData担当の発信者は -[NSData(NSData) copyWithZone:]です。

Leaks Instrument を使用してプロファイリングすると、リークは見つかりません。

私はObjective-Cが初めてで、私が理解したことから、新しいARCはメモリの割り当てと割り当て解除を処理することになっています。メモリに関連するものをグーグルで検索すると、ARCを使用していない(または執筆時点では存在していなかった)と仮定した情報がすべて見つかります。手動でメモリの割り当てを解除しようとするとコンパイルエラーが発生するので、確かにARCを使用しています。

誰かがこれで私を助けることができれば、それは大歓迎です! さらに情報が必要な場合は、喜んで提供させていただきます:) また、私は StackOverflow を初めて使用するので、見落としていることがあれば、お知らせください。

4

4 に答える 4

7

最後に、コールバックに依存してストリームを待つのではなく、セマフォを使用するこちらのソリューションを試しました。これは完全に機能します :) Allocations Instrument によると、メモリ使用量は約 1.1 MB です。セマフォの構文が原因で見た目がすっきりしないかもしれませんが、少なくとも必要なことは実行できます。

もちろん、他の提案も大歓迎です:)

- (void)encryptWithSemaphore:(NSURL *)url {
  dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);

  __block int total = 0;
  int blockSize = 32 * 1024;

  NSString *encryptedFile = [[url lastPathComponent] stringByDeletingPathExtension];
  NSURL *docsURL = [[NSFileManager defaultManager] URLForDirectory:NSDocumentDirectory inDomain:NSUserDomainMask appropriateForURL:nil create:NO error:nil];
  self.tempURL = [[docsURL URLByAppendingPathComponent:encryptedFile isDirectory:NO] URLByAppendingPathExtension:@"crypt"];

  NSInputStream *inputStream = [NSInputStream inputStreamWithURL:url];
  __block NSOutputStream *outputStream = [NSOutputStream outputStreamWithURL:self.tempURL append:NO];
  __block NSError *encryptionError = nil;

  [inputStream open];
  [outputStream open];

  RNEncryptor *encryptor = [[RNEncryptor alloc] initWithSettings:kRNCryptorAES256Settings
                                                        password:self.password
                                                         handler:^(RNCryptor *cryptor, NSData *data) {
                                                           @autoreleasepool {
                                                             [outputStream write:data.bytes maxLength:data.length];
                                                             dispatch_semaphore_signal(semaphore);

                                                             data = nil;
                                                             if (cryptor.isFinished) {
                                                               [outputStream close];
                                                               encryptionError = cryptor.error;
                                                               // call my delegate that I'm finished with decrypting
                                                             }
                                                           }
                                                         }];
  while (inputStream.hasBytesAvailable) {
    @autoreleasepool {
      uint8_t buf[blockSize];
      NSUInteger bytesRead = [inputStream read:buf maxLength:blockSize];
      if (bytesRead > 0) {
        NSData *data = [NSData dataWithBytes:buf length:bytesRead];

        total = total + bytesRead;
        [encryptor addData:data];

        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
      }
    }
  }

  [inputStream close];
  [encryptor finish];  
}
于 2013-03-12T11:04:30.980 に答える
4

とにかく走れ:

self.cryptorQueue = dispatch_queue_create([queueName cStringUsingEncoding:NSUTF8StringEncoding], NULL);

dispatch_async(self.cryptorQueue, ^{
        readStreamBlock();
    });

問題: スタックが大きくなり、autorelease pull がバッファのリリースを実行しません。

解決策:同じキューに非同期を追加します。これにより、現在のブロックの実行が終了します。

コードは次のとおりです。

__block NSMutableData *data = [NSMutableData dataWithLength:blockSize];
__block RNDecryptor *decryptor = nil;


dispatch_block_t readStreamBlock = ^{

    [data setLength:blockSize];

    NSInteger bytesRead = [inputStream read:[data mutableBytes] maxLength:blockSize];
    if (bytesRead < 0) {
        // Throw an error
    }
    else if (bytesRead == 0) {
        [decryptor finish];
    }
    else {

        [data setLength:bytesRead];
        [decryptor addData:data];
    }
};

decryptor = [[RNDecryptor alloc] initWithPassword:@"blah" handler:^(RNCryptor *cryptor, NSData *data) {

        [decryptedStream write:data.bytes maxLength:data.length];
        _percentStatus = (CGFloat)[[decryptedStream propertyForKey:NSStreamFileCurrentOffsetKey] intValue] / (CGFloat)_inputFileSize;
        if (cryptor.isFinished)
        {
            [decryptedStream close];
            [self decryptFinish];
        }
        else
        {
            dispatch_async(cryptor.responseQueue, ^{
                readStreamBlock();
            });
            [self decryptStatusChange];
        }

}];


// Read the first block to kick things off

decryptor.responseQueue = self.cryptorQueue;
[self decryptStart];
dispatch_async(decryptor.cryptorQueue, ^{
    readStreamBlock();
});
于 2013-05-07T11:15:18.087 に答える
0

私は再び間違っているかもしれませんが、あなたのreadStreamBlock,は、ブロックの外側で宣言されdataたへの参照ではなく、ブロックへのパラメーターである必要があります。__block NSMutableDataご覧のとおり、RNEncryptorハンドラーは独自のdata変数を提供します。これは、自分で宣言したものとは異なります。

理想的にはreadStreamBlock、ブロックを宣言することさえせずに、ハンドラー内にすべてを直接配置します。

于 2013-03-11T12:49:33.280 に答える
0

私は間違っているかもしれませんが、あなたのdo...whileループは自動解放プールが十分に頻繁に排出されるのを防いでいると思います。

このループを使用して、デクリプターが終了するのを待つのはなぜですか? 代わりに、完了ブロックを使用して、drcryptor が終了したことをコントローラーに通知する必要があります。

(ところで、SO へようこそ。あなたの質問は本当によく尋ねられており、高く評価されています)。

于 2013-03-11T11:01:04.897 に答える